Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
- Treat compaction and principal replacement as lossy handoffs. Preserve the
objective in principal-authored goals, plans, catalogs, ledgers, assignments,
and checkpoints before context pressure forces recovery.
- Delegates own assigned artifacts and return evidence. The user-facing
principal reviews assignments and remains the logical author of control
state.
- Delegates own assigned artifacts and return evidence through the named return
channel. The user-facing principal reviews assignments and remains the logical
author of control state.
- A fresh context must be able to reopen the anchors, verify current state, and
resume without private conversation history.

Expand Down
6 changes: 3 additions & 3 deletions codex/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
objective in principal-authored goals, plans, catalogs, ledgers, assignments,
and checkpoints before context pressure forces recovery.
- Delegates own assigned artifacts and return evidence through the named return
channel. The principal reviews assignments and remains the logical author of
control state.
channel. The user-facing principal reviews assignments and remains the logical
author of control state.
- A fresh context must be able to reopen the anchors, verify current state, and
resume without relying on private conversation history.
resume without private conversation history.
- A work catalog may record a blocker immediately. Mark the active Codex goal
blocked only when the same blocking condition has repeated for at least three
consecutive goal turns and no meaningful progress remains without user input
Expand Down
22 changes: 12 additions & 10 deletions local-docs/source-of-truth.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,18 @@ Status values:
| 10 | Skill-description length cap | scripts/common.ps1 MaxSkillDescriptionLength | pin | test-compass-architecture.py | consolidated |
| 11 | Routing source reference (checklist prose deferred) | workflows/addition-intake.md | pin | policy-contracts.ps1 | consolidated |
| 12 | Glossary terms | glossary.md | link | editorial convention | accepted |
| 13 | Shared user-preference prose | manifests/policy-contracts.json | pin | policy-contracts.ps1 | consolidated |
| 13 | Shared runtime-global prose | manifests/policy-contracts.json | pin | policy-contracts.ps1 | consolidated |

## Intentional separations

These duplications are deliberate and stay. They are listed here so future
audits do not re-flag them.

- `codex/AGENTS.md`, `claude/CLAUDE.md`, and `apps/compass-mcp/profile.md`
serve different runtimes. Policy contracts pin their complete shared Writing,
Git, and PR block while the runtime-specific deltas stay separate.
serve different runtimes. Policy contracts pin the applicable shared blocks:
Writing, Git, and PR prose across all three surfaces, plus continuity,
planning, repository, and Windows prose across the Codex and Claude globals.
Runtime-specific deltas stay separate.
- `workflows/templates/*.md` (maintainer, long form),
`codex/skills/workspace-steward/references/project-template` copies (starter
pack), and the embedded blocks in
Expand Down Expand Up @@ -78,13 +80,13 @@ audits do not re-flag them.
`template-anchors.ps1` pin covers the shared goal, catalog, assignment, and
checkpoint identities, plus plan and decision identities where both file
families provide those artifacts, so the registered binding is consolidated.
- Cluster 13 (shared user-preference prose): the verbatim Writing and Git
sections across `codex/AGENTS.md`, `claude/CLAUDE.md`, and
`apps/compass-mcp/profile.md` are bound by policy-contract pins so drift fails
doctor. Full generation (a `generate-runtime-globals.py` reassembling each
file from a shared source plus runtime deltas) was deferred because these are
load-bearing runtime instructions; the pin is the safe fallback authorized in
the plan.
- Cluster 13 (shared runtime-global prose): policy contracts pin the verbatim
Writing, Git, and PR blocks across `codex/AGENTS.md`, `claude/CLAUDE.md`, and
`apps/compass-mcp/profile.md`, plus the shared continuity, planning,
repository, and Windows blocks across the Codex and Claude globals. Full
generation (a `generate-runtime-globals.py` reassembling each file from a
shared source plus runtime deltas) was deferred because these are load-bearing
runtime instructions; the pin is the safe fallback authorized in the plan.
- Cluster 11 (routing table and update-together checklist): the canonical
routing source `workflows/addition-intake.md` is already referenced from the
key surfaces, and policy contracts now require the two skill-authoring
Expand Down
20 changes: 20 additions & 0 deletions manifests/policy-contracts.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@
"## Writing\n\n- Never use em dashes or en dashes in commits, code comments, prose, or chat.\n Use other punctuation or separate sentences.\n\n## Git And Pull Requests\n\n- Commit with one lowercase subject, about 8 words or fewer, with no trailing\n punctuation, body, or `Co-Authored-By` trailer. Describe changes neutrally.\n- Prefer a pull request as the unit for repository changes. Use a descriptive,\n often verb-led title without conventional-commit prefixes. Keep the body to\n 0-2 motivation-first sentences with no headers, checkboxes, emojis, or\n generated footer."
]
},
{
"id": "shared-runtime-prose-codex",
"path": "codex/AGENTS.md",
"exact_sections": {
"Long-Running Continuity": "- Treat compaction and principal replacement as lossy handoffs. Preserve the objective in principal-authored goals, plans, catalogs, ledgers, assignments, and checkpoints before context pressure forces recovery.\n- Delegates own assigned artifacts and return evidence through the named return channel. The user-facing principal reviews assignments and remains the logical author of control state.\n- A fresh context must be able to reopen the anchors, verify current state, and resume without private conversation history.\n- A work catalog may record a blocker immediately. Mark the active Codex goal blocked only when the same blocking condition has repeated for at least three consecutive goal turns and no meaningful progress remains without user input or an external state change. Count the original or user-triggered turn and any automatic continuations. After a blocked goal resumes, start a fresh blocked audit and require three consecutive resumed goal turns before blocking it again. Once those conditions hold, mark the goal blocked instead of leaving it active while repeatedly reporting the blocker.",
"Planning Authority": "- A planning-only request authorizes inspection, research, questions, plans, and explicitly scoped experiments. Production implementation begins when the user or named authority opens that phase.\n- Review material goals, plans, delegation boundaries, and irreversible actions before launch unless the user already granted or waived that review.",
"Repository And State Boundaries": "- In a worktree, read and edit only that worktree. Keep one scope per branch or pull request, and preserve unrelated user work.\n- Prefer the smallest coherent change that fixes the owning boundary. After a repair, review for removable guards, wrappers, duplicate state, and obsolete paths.",
"Windows": "- Container entrypoint `.sh` files use LF line endings. When startup reports `No such file or directory`, inspect line endings before assuming a missing path."
}
},
{
"id": "shared-runtime-prose-claude",
"path": "claude/CLAUDE.md",
"exact_sections": {
"Long-Running Continuity": "- Treat compaction and principal replacement as lossy handoffs. Preserve the objective in principal-authored goals, plans, catalogs, ledgers, assignments, and checkpoints before context pressure forces recovery.\n- Delegates own assigned artifacts and return evidence through the named return channel. The user-facing principal reviews assignments and remains the logical author of control state.\n- A fresh context must be able to reopen the anchors, verify current state, and resume without private conversation history.",
"Planning Authority": "- A planning-only request authorizes inspection, research, questions, plans, and explicitly scoped experiments. Production implementation begins when the user or named authority opens that phase.\n- Review material goals, plans, delegation boundaries, and irreversible actions before launch unless the user already granted or waived that review.",
"Repository And State Boundaries": "- In a worktree, read and edit only that worktree. Keep one scope per branch or pull request, and preserve unrelated user work.\n- Prefer the smallest coherent change that fixes the owning boundary. After a repair, review for removable guards, wrappers, duplicate state, and obsolete paths.",
"Windows": "- Container entrypoint `.sh` files use LF line endings. When startup reports `No such file or directory`, inspect line endings before assuming a missing path."
}
},
{
"id": "routing-source-referenced-by-compass-skill",
"path": "codex/skills/compass/SKILL.md",
Expand Down
59 changes: 50 additions & 9 deletions scripts/validate-policy-contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,38 @@ def safe_repo_path(root: Path, value: object) -> Path:
return resolved


def string_list(contract_id: str, field: str, value: object, *, required: bool) -> list[str]:
if value is None and not required:
def string_list(contract_id: str, field: str, value: object) -> list[str]:
if value is None:
return []
if not isinstance(value, list) or (required and not value):
qualifier = "non-empty " if required else ""
raise ValueError(f"contract {contract_id}: {field} must be a {qualifier}string array")
if not isinstance(value, list):
raise ValueError(f"contract {contract_id}: {field} must be a string array")
if any(not isinstance(item, str) or not item.strip() for item in value):
raise ValueError(f"contract {contract_id}: {field} must contain only non-empty strings")
return value


def string_mapping(contract_id: str, field: str, value: object) -> dict[str, str]:
if value is None:
return {}
if not isinstance(value, dict) or not value:
raise ValueError(f"contract {contract_id}: {field} must be a non-empty string map")
if any(not isinstance(key, str) or not key.strip() for key in value):
raise ValueError(f"contract {contract_id}: {field} keys must be non-empty strings")
if any(not isinstance(item, str) or not item.strip() for item in value.values()):
raise ValueError(f"contract {contract_id}: {field} values must be non-empty strings")
return value


def markdown_section(text: str, heading: str) -> str | None:
pattern = re.compile(
rf"(?ms)^##\s+{re.escape(heading)}\s*$\n(.*?)(?=^##\s+|\Z)"
)
match = pattern.search(text)
if match is None:
return None
return match.group(1)


def validate(root: Path, manifest_path: Path) -> list[str]:
manifest = load_manifest(manifest_path)
problems: list[str] = []
Expand All @@ -80,25 +101,35 @@ def validate(root: Path, manifest_path: Path) -> list[str]:
continue
seen_ids.add(contract_id)

unknown = sorted(set(raw_contract) - {"id", "path", "required", "forbidden"})
unknown = sorted(
set(raw_contract) - {"id", "path", "required", "forbidden", "exact_sections"}
)
if unknown:
problems.append(f"contract {contract_id}: unsupported fields: {', '.join(unknown)}")
continue

try:
path = safe_repo_path(root, raw_contract.get("path"))
required_phrases = string_list(
contract_id, "required", raw_contract.get("required"), required=True
contract_id, "required", raw_contract.get("required")
)
forbidden_phrases = string_list(
contract_id, "forbidden", raw_contract.get("forbidden"), required=False
contract_id, "forbidden", raw_contract.get("forbidden")
)
exact_sections = string_mapping(
contract_id, "exact_sections", raw_contract.get("exact_sections")
)
if not required_phrases and not exact_sections:
raise ValueError(
f"contract {contract_id}: requires required text or exact_sections"
)
except ValueError as error:
problems.append(str(error))
continue

try:
content = normalize(path.read_text(encoding="utf-8"))
raw_content = path.read_text(encoding="utf-8")
content = normalize(raw_content)
except FileNotFoundError:
problems.append(f"contract {contract_id}: missing file: {path.relative_to(root)}")
continue
Expand All @@ -116,6 +147,16 @@ def validate(root: Path, manifest_path: Path) -> list[str]:
problems.append(
f"contract {contract_id}: forbidden text present in {path.relative_to(root)}: {phrase}"
)
for heading, expected_body in exact_sections.items():
actual_body = markdown_section(raw_content, heading)
if actual_body is None:
problems.append(
f"contract {contract_id}: missing section in {path.relative_to(root)}: {heading}"
)
elif normalize(actual_body) != normalize(expected_body):
problems.append(
f"contract {contract_id}: section drift in {path.relative_to(root)}: {heading}"
)

return problems

Expand Down