From 795073126c904717717d86fbe753901687c2f2e7 Mon Sep 17 00:00:00 2001 From: HLLMR Date: Mon, 14 Sep 2026 16:55:28 -0500 Subject: [PATCH] Preserve scoped authorization across agent handoffs (#35) Signed-off-by: HLLMR --- ADOPTING.md | 38 +++++++- PROJECTION-MANIFEST.sha256 | 28 +++--- PROJECTION-PROVENANCE.md | 4 +- START-HERE.md | 38 +++++++- checks/check_coordinator_release.py | 81 ++++++++++++++++ governance/LOG-denials-probes.md | 18 ++++ governance/LOG-denials.jsonl | 5 + governance/LOG.md | 33 +++++++ governance/PLAN.md | 20 ++++ governance/STATE.md | 28 ++++++ identity/legacy-references.json | 8 +- scripts/start_writwall.py | 78 ++++++++++++++- skills/writwall-adopt/SKILL.md | 38 +++++++- tests/test_coordinator_release.py | 54 +++++++++++ tests/test_start_writwall.py | 141 ++++++++++++++++++++++++++++ 15 files changed, 587 insertions(+), 25 deletions(-) diff --git a/ADOPTING.md b/ADOPTING.md index 9ceb445..597decc 100644 --- a/ADOPTING.md +++ b/ADOPTING.md @@ -372,12 +372,48 @@ with a concise Recommendation and material tradeoff; keep the detailed packet be supporting evidence rather than the conversational front door. When the next safe mechanical action is available, ask once for one combined disposition and action. If that action uses a new user-owned task, explicitly include creation and dispatch of the named task in that approval -request; never infer task-creation permission afterward. Once approved, perform every +request; never infer task-creation permission afterward. Carry that approval's continuity in the +shared Authorization section below, transcribed from an already-authorized current record rather +than retyped or re-approved by the Owner. + +## Authorization + +- Approval source/reference: unknown: not yet transcribed from an already-authorized record +- Approved action: unknown: not yet transcribed from an already-authorized record +- Exact scope: unknown: not yet transcribed from an already-authorized record +- Exclusions: unknown: not yet transcribed from an already-authorized record +- Delegation permission: unknown: not yet transcribed from an already-authorized record +- Lifecycle conditions: unknown: not yet transcribed from an already-authorized record +- Completion boundary: unknown: not yet transcribed from an already-authorized record + +This section carries forward evidence of a decision already made elsewhere; +it is not itself a decision, and it never substitutes for an independent +provider authorization. A field populated above transcribes that +already-authorized record's own reference and wording; the human Owner never +retypes or re-approves it. A field left unknown above means the preparer has +not yet located it in an already-authorized current record; the preparer +inspects those records before asking anyone. Only a genuinely missing, materially necessary decision is a question for the Owner; the absence of optional or formal metadata is not itself an approval loop, and an existing valid legacy approval remains usable without new paperwork. + +Matching current approval: performs the already-authorized action once the provider itself permits it. +Missing approval: says plainly that authorization is missing and stops. +Explicit revocation or supersession: treats a revoked or superseded record as no longer authorizing anything. +Requested action beyond scope: performs only the authorized part and names the excess as unauthorized. +Independent provider denial: reports the provider's own denial as the exact blocker. +Environment prerequisite failure: names the exact missing or failed environment prerequisite as the blocker. +Unapproved task creation or data transmission: never creates or transmits a task, message, or dataset outside the approved action. + +Once approved, perform every mechanically available authorized step. Do not ask for the same decision again. The human Owner alone ratifies intent and activates work; preserve a distinct fresh Reviewer after implementation. The onboarding coordinator stops here and does not continue into project work. ``` +The Authorization section above is filled in by the General itself from +already-approved current records, or an equivalent legacy record's existing +scope and authority; the Owner is never asked to retype or re-approve values +that already exist, and a blank field alone is not a new approval service or +a performance claim. + The General prepares whatever genuine work the project needs next (6.1.4), including a bounded external Operator packet when that is smaller than a repository work order. It leads with its recommendation and material tradeoff; diff --git a/PROJECTION-MANIFEST.sha256 b/PROJECTION-MANIFEST.sha256 index 5f74210..aa22989 100644 --- a/PROJECTION-MANIFEST.sha256 +++ b/PROJECTION-MANIFEST.sha256 @@ -5,7 +5,7 @@ b2e36dfcfc6eb31570c9340640bcd73abc62f57c80b4794abf36e3d3e89ab34f .github/depend 9e90d43615b02a265b08692ef7a1c00a37477a5c6b1e8233a8fc7bedefaedea6 .github/pull_request_template.md 3c35b31bc2b80d101a3a549da68fec55587b6a6428ce6b32112670276490ce23 .github/workflows/ci.yml e544abe8ffd83c81c7b002cbd2e552f9d56f226ea20e1e0722c5d1bdec914fe0 .gitignore -3ab922d5b7962f571f0680224a52b55a84c0b0b9a6d1a5dfd775697172338582 ADOPTING.md +fa861d27b6b770ab51318307bfe72b4eb8f62bf70de6cec798ca7f2e2ac2843c ADOPTING.md 1179c999034f4ec1c1d44c1946bd2955c4625905e80767abe760c8c3ab01c493 CLAUDE.md 074908ecfc14027823851f9cea118a985c85dd947c17870a44f9c903cd28a348 CONTRIBUTING.md 664196054cd98585105be457afa09c788a482416ccb48a87bb269b2156e49ae6 DOCTRINE.md @@ -16,17 +16,17 @@ c274f80372d90c012937370f0e1f15087d22e308ef98b27cea5dc0d2d088366c LICENSES/Apach a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499 LICENSES/CC0-1.0.txt 59746d6285ffa44bfc7ecada352aa5d6a20dc8eab418a60ce091cc739012c135 LICENSES/MIT-0.txt 35e6d37b7c5fa0c1fc872315cbd362cd24bfa41e1b7dc3019fbcd31e99350f51 NAMING.md -3bc783a329e09fb149c0c9f6e8da9187d8bf9a8a753350d54c78900431c7d7d7 PROJECTION-PROVENANCE.md +f0781af7ed52bc48148daf1ffdb65e663719709692ba19ce01c462c4a326fd63 PROJECTION-PROVENANCE.md 5dec4f02eeaef72ea93b66dd548520ef5af6c7d246b261a5c82804da94f71b63 PUBLICATION.md bb9083c43def4a803c3f01e296ccbdb0402068ec39145e3ddd892ac3e922eea3 README.md 284a0862f3be77e8d867aa4d3ef92ed1a64ad4315d6d9074f6bb64771b6d1dd0 REUSE.toml ab75b39490b4db4e203f5b23b480a1c998d87cf07d760cb787cb260778b21d0a SECURITY.md 6a51c1211cc675599634d144ca24a705ec1696f84640a6464b14efb1d6c3a629 SELF-HOSTING.md -d4fc267f4ebacad09ef6c357aa339c0fb22e1a9b75392fcf80e3ec3ebff0caf7 START-HERE.md +b0df02f0971953e26550bbb5772ffc0914b4caa2c6d317c7c7bd76246de0e336 START-HERE.md 75c7ae0f569148f489570d63df916a70b6ccf24b77db2076cdee29663f747428 adapters/claude-code/README.md aeb7f81d139e7ffa6de9a1782444b99eb6d549ac1c3a8b9c0f8bcb9c6addfa6d adapters/claude-code/SECURITY.md dd29af2a39d25e0270ad9acc23ee912f81e39c674e1179759f4a3010c6a0c1a0 adapters/claude-code/wo_capability_wall.py -edfc5e7622a7a48c826e8bad5192d072ad3f0e3f29fa897f3e607cb04191cb9d checks/check_coordinator_release.py +2e3b74cc0fe42f790c2a7b066a7bb7f06c089b426ad5c57e9e2df909b024e6e6 checks/check_coordinator_release.py 20df8e937b6efeb7930894b7d4eba42761283b6d0166e78bcabaa2ab6dc75a7c checks/check_distribution.py 60fe377dac32b8d1697f859371ef40d26ed4e695fdceeda6d29ec0318d539504 checks/check_identity.py 30986c40ff7c9b29e2fba39ec04c18af3c1c351490410bd532a8391bcb92ed11 checks/check_licenses.py @@ -60,12 +60,12 @@ dbab45d15702d32ea745076b0dee05c293346b4f42581fd2cb869deb1c5b51b3 examples/name- d2c5a8ca21edf842dfd17a83862024afa0a92349abf693a60e55ce454c8d78fa examples/name-clearance-ledgers/writwall-candidate.json 0d62666ddc07a4283309bcbc8f9501add4ecec052d26369d30ce232e17c17702 examples/plumbline-self-hosting-pilot.md 30cdb11fbeb2fd9bbf4048255331ccbbdd6e516fae5adfa5317af00ce53607c9 governance/ADOPTION-MAPPING.md -08b235351ab7799715b1e2df4fa3dd9fa88bb85084c8aade4d01039bf255b489 governance/LOG-denials-probes.md -5dba70f7d7263ecd10ecb2c18867b6c2c5870a3e001c25c7797d3d61feae680a governance/LOG-denials.jsonl -ffa9029e76fbe660f9c4eced42b11960bfd96ef0451f7d18e874c349de264aba governance/LOG.md -ed0bdab770d734e83debbecc4d740f95018b39a290946d1941e2bbec09f57ee0 governance/PLAN.md +b3efd4f1d8f033f16e4d95a25b521c5ab4b492f0c80776a573389197863b7c6d governance/LOG-denials-probes.md +2511dabaa26521cf6d07ac04e8a5886348e8ef1cb6d64ba46598f97f89eacdcb governance/LOG-denials.jsonl +296a97522e331edb71f53cbae4e86bc35560df501e4a020eb1547899fe29a3b2 governance/LOG.md +e27b792bf3e315ef24f97a35b7463fd788de6692657b123384f62d5c720f64d2 governance/PLAN.md dd445eb2994e0d9615bc61fe2ae157a6b14ba7b190c65b705d380b31c49fdfe0 governance/ROUTING.md -646887c40607a97b418ec8f09be1c94c51f7eb053bca08908441007a397d31c7 governance/STATE.md +ab7aaa8c93e41907b1c7e5767c5b2fcbbd1c15471cd747f54e9b695b25da283a governance/STATE.md e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/archive/.gitkeep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/briefs/.gitkeep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/decisions/.gitkeep @@ -82,7 +82,7 @@ b567ce0c0867464328e81774d888f6491fa66b68ac73be01f993e5c4c66d3ed8 governance/tem d355e46f978f17de8824af805e05124e0f20b1c072523b518422044f88c6f079 governance/templates/D-adoption-record.md 2b586efadab716a59fcafb74312a45a05401a4787fee6ae18cb5c9dd14ef3a09 governance/templates/E-adoption-mapping.md e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/work-orders/.gitkeep -9e6cb17ec4a468bdd9e0f60333d9ea77b83579e4f3c7926e87d2ab9b34cc077f identity/legacy-references.json +e92c1cacfabef866972baeed1aa0a68a8817a5aa81f382bbfb10c8e085f2a3eb identity/legacy-references.json 345b7e962731c085a95aea66a344eae000b27c9bde13b0d790c76b73273dbe7a init.sh 5c90584642f405534b2071f27396ff293ab01632e4dbb8ccb6b8ec043dca4cc9 migration-guides/0.1-to-0.6.md ba4eff258ca5b9a45f3f9f1cbf646ba5bc5521fae812adacac65cd2e78698c9d migration-guides/0.6-to-0.7.md @@ -93,9 +93,9 @@ fafcbf659c40d7260dddaa8a64b6b59c3b7de484f77d879a90323bb4b3e1a4a7 projection/pub 08fa88f3a1de7a26c14c383ea3f18e86838499d9eacb7cf57819fb27c2f20c30 scripts/build_public_projection.py 3cf88f936599e0e84bc2368bc0503a39b9f96e47b473e09b26569e5c3c9edbd9 scripts/collect_name_clearance.py c372f7f1736eb77bedaca43696ea0b060333733f912053479b363442022c4b24 scripts/privacy_screen.py -61f8f3302322b704a090c6e4b5110c8189687705be244adcdd7d997e2bc0a9a9 scripts/start_writwall.py +682ad647c8518039619f4cecfcdedf0375cde16045e6961bbb0839aae00d839f scripts/start_writwall.py 374f4e8a80b7b9e162b9360a3907b6ffe12ce94ba0ed827058c7b3c9c0658b2c skills/writwall-adopt/LICENSE-MAP.md -02e75310dca3d528b4c5cd2ee9d8a57a89390365037c941010a6b2c1c9eb918e skills/writwall-adopt/SKILL.md +5cf8e93cf18d848a1c0cecd426af4ef48142d554990e1d7118598e608794c8f7 skills/writwall-adopt/SKILL.md 75c7ae0f569148f489570d63df916a70b6ccf24b77db2076cdee29663f747428 skills/writwall-adopt/assets/adapters/claude-code/README.md dd29af2a39d25e0270ad9acc23ee912f81e39c674e1179759f4a3010c6a0c1a0 skills/writwall-adopt/assets/adapters/claude-code/wo_capability_wall.py 7cfd0ae28d07cdfbb367adc4f7e606a1538ebf61ff140a32f8e793028110cedf skills/writwall-adopt/assets/bootstrap-charter-addendum.md @@ -121,14 +121,14 @@ d355e46f978f17de8824af805e05124e0f20b1c072523b518422044f88c6f079 templates/D-ad 9924816cbbeade6f88f79d3e06fe04d143d801783888210ac2325925d69e4bdb tests/test_check_distribution.py b046f2eea794070194294a33f2914e627eed384e63fccffc2ac46693db2a968c tests/test_check_licenses.py 9a106ff5182b4a15713575de42e90b0dc5cdeebcb17ba08d97522a4c9aa6b2fa tests/test_check_work_order_dispatch.py -a1f3472131beec3cd44fdd389d836a6a86b4269b94226b159e223b602bb8b32f tests/test_coordinator_release.py +2157302d41373d39ed27fbccbc0d3512cb541029fdeb9cb807af4da474926ab0 tests/test_coordinator_release.py 13478c88a9d9951f4a90ef15fa389fc0bf19894f917cbada62fc366cbe70a635 tests/test_distribution.py e150a2f988a4b0beac5f70644f55f5e185a8aa575e988a19642bafabc0f07775 tests/test_identity_migration.py 11cd8090dbc53e8aa6a2f14cb181c8a11696335da8f40700eae5116798e49ba5 tests/test_init_sh.py a2df93f79791a884d9c6c2db308591a3b18e95ee34702ad5330ccc7a4b683fa4 tests/test_name_clearance.py 677d5b532450ace267be9c834269c081368697cd83defa5531ce673f6d0ca252 tests/test_privacy_screen.py 0d1d5cc19779e3539d46caba978fe18de7af751c8c98fe435dbf2e924860ca81 tests/test_public_projection.py -ef2ee15da47fa329f00961517d306fc8bfcba1b7514810fe5b006fdbb028e5fe tests/test_start_writwall.py +8be8ab796e479294a8ddbab7f45a8555e3fe47075d60c414682cbadff74b8ee6 tests/test_start_writwall.py 0684c04067eb95eadc9f72ab126d8662b4a5e2005c80b2dea174075a6140eebc tests/test_wo_capability_wall.py e8caf7f4421dc7f78b0d766741ec2ef4c2ac6dab6117fab6e4175b27d31e4d49 writwall_cli/__init__.py aee1a6d4437f468c6c21cef95e3e45181ed40ca402a11092fed5db587865c21f writwall_cli/__main__.py diff --git a/PROJECTION-PROVENANCE.md b/PROJECTION-PROVENANCE.md index 080af18..31560ab 100644 --- a/PROJECTION-PROVENANCE.md +++ b/PROJECTION-PROVENANCE.md @@ -5,8 +5,8 @@ Legacy commit identifiers in projected records refer to that private source and are intentionally not resolvable from fresh public history. No private remote URL is recorded here. -- Source commit: `c8ad85737b7cd60360cee7561f28d666a9b73080` -- Source commit time: `2026-09-04T19:05:51-05:00` +- Source commit: `f80fb4db4716540b49def2af100e0ee92b651384` +- Source commit time: `2026-09-14T16:09:45-05:00` - Projection allowlist SHA-256: `fafcbf659c40d7260dddaa8a64b6b59c3b7de484f77d879a90323bb4b3e1a4a7` ## Legacy identifier inventory diff --git a/START-HERE.md b/START-HERE.md index 256b0b2..182e68d 100644 --- a/START-HERE.md +++ b/START-HERE.md @@ -402,12 +402,48 @@ with a concise Recommendation and material tradeoff; keep the detailed packet be supporting evidence rather than the conversational front door. When the next safe mechanical action is available, ask once for one combined disposition and action. If that action uses a new user-owned task, explicitly include creation and dispatch of the named task in that approval -request; never infer task-creation permission afterward. Once approved, perform every +request; never infer task-creation permission afterward. Carry that approval's continuity in the +shared Authorization section below, transcribed from an already-authorized current record rather +than retyped or re-approved by the Owner. + +## Authorization + +- Approval source/reference: unknown: not yet transcribed from an already-authorized record +- Approved action: unknown: not yet transcribed from an already-authorized record +- Exact scope: unknown: not yet transcribed from an already-authorized record +- Exclusions: unknown: not yet transcribed from an already-authorized record +- Delegation permission: unknown: not yet transcribed from an already-authorized record +- Lifecycle conditions: unknown: not yet transcribed from an already-authorized record +- Completion boundary: unknown: not yet transcribed from an already-authorized record + +This section carries forward evidence of a decision already made elsewhere; +it is not itself a decision, and it never substitutes for an independent +provider authorization. A field populated above transcribes that +already-authorized record's own reference and wording; the human Owner never +retypes or re-approves it. A field left unknown above means the preparer has +not yet located it in an already-authorized current record; the preparer +inspects those records before asking anyone. Only a genuinely missing, materially necessary decision is a question for the Owner; the absence of optional or formal metadata is not itself an approval loop, and an existing valid legacy approval remains usable without new paperwork. + +Matching current approval: performs the already-authorized action once the provider itself permits it. +Missing approval: says plainly that authorization is missing and stops. +Explicit revocation or supersession: treats a revoked or superseded record as no longer authorizing anything. +Requested action beyond scope: performs only the authorized part and names the excess as unauthorized. +Independent provider denial: reports the provider's own denial as the exact blocker. +Environment prerequisite failure: names the exact missing or failed environment prerequisite as the blocker. +Unapproved task creation or data transmission: never creates or transmits a task, message, or dataset outside the approved action. + +Once approved, perform every mechanically available authorized step. Do not ask for the same decision again. The human Owner alone ratifies intent and activates work; preserve a distinct fresh Reviewer after implementation. The onboarding coordinator stops here and does not continue into project work. ``` +The Authorization section above is filled in by the General itself from +already-approved current records, or an equivalent legacy record's existing +scope and authority; you are never asked to retype or re-approve values that +already exist, and a blank field alone is not a new approval service or a +performance claim. + The General leads with a concise recommendation and material tradeoff; its detailed packet remains supporting evidence. If the next safe step can be done, its one approval request includes both the disposition and that action. Creating diff --git a/checks/check_coordinator_release.py b/checks/check_coordinator_release.py index 82f7193..e4b1232 100644 --- a/checks/check_coordinator_release.py +++ b/checks/check_coordinator_release.py @@ -51,6 +51,29 @@ ) MAX_RELEASE_METADATA_BYTES = 1024 * 1024 +# Kept byte-for-byte identical to scripts/start_writwall.py's +# authorization_continuity_block() field labels and B.3.4 outcome sentences. +# This installed-output gate proves the actual emitted contract, not a +# semantic authorization parser or a claim of independent provider proof. +AUTHORIZATION_CONTINUITY_LABELS = ( + "Approval source/reference:", + "Approved action:", + "Exact scope:", + "Exclusions:", + "Delegation permission:", + "Lifecycle conditions:", + "Completion boundary:", +) +AUTHORIZATION_CONTINUITY_OUTCOMES = ( + "performs the already-authorized action once the provider itself permits it", + "says plainly that authorization is missing and stops", + "treats a revoked or superseded record as no longer authorizing anything", + "performs only the authorized part and names the excess as unauthorized", + "reports the provider's own denial as the exact blocker", + "names the exact missing or failed environment prerequisite as the blocker", + "never creates or transmits a task, message, or dataset outside the approved action", +) + class ReleaseCheckError(RuntimeError): """A bounded, user-facing release-candidate failure.""" @@ -289,6 +312,25 @@ def python_bytecode_residue(root: Path) -> list[str]: ) +def verify_authorization_continuity_content(text: str, surface: str) -> None: + """Require every B.3.3 field label and B.3.4 outcome sentence verbatim. + + A missing item names the surface and the exact missing text; this is a + content presence check against the one shared generator, never a + semantic parser and never proof of independent provider enforcement. + """ + missing = [ + item + for item in (*AUTHORIZATION_CONTINUITY_LABELS, *AUTHORIZATION_CONTINUITY_OUTCOMES) + if item not in text + ] + if missing: + raise ReleaseCheckError( + f"authorization-continuity content missing in {surface}: " + + "; ".join(missing) + ) + + def check_candidate(candidate: Path, expected_tag: str) -> None: candidate = candidate.resolve() if not candidate.is_dir(): @@ -456,6 +498,7 @@ def check_candidate(candidate: Path, expected_tag: str) -> None: "--environment", "disposable local external project", "--owner-time", "no", "--confirm-no-secrets", + "--external-operator", "Synthetic release-check function", ], cwd=workspace, environment=environment, @@ -474,6 +517,22 @@ def check_candidate(candidate: Path, expected_tag: str) -> None: "complete handoff contains Python bytecode residue: " + ", ".join(residue) ) + for relative in ("GENERAL.md", "OPERATOR.md", "REPOSITORY-OPERATOR.md"): + verify_authorization_continuity_content( + (output / relative).read_text(encoding="utf-8"), + f"installed {relative}", + ) + operator_packets = sorted((output / "operations").glob("*.md")) + if not operator_packets: + raise ReleaseCheckError( + "installed coordinator run produced no external Operator packet " + "for the requested synthetic function" + ) + for packet_path in operator_packets: + verify_authorization_continuity_content( + packet_path.read_text(encoding="utf-8"), + f"installed external Operator packet {packet_path.name}", + ) intake_payload = json.loads( (output / "intake.json").read_text(encoding="utf-8") @@ -525,6 +584,9 @@ def check_candidate(candidate: Path, expected_tag: str) -> None: "installed adopted-lockout route omitted: " + ", ".join(missing_route_text) ) + verify_authorization_continuity_content( + adopted_result.stdout, "installed adopted-lockout start output (General)" + ) if (adopted / ".writwall-bootstrap").exists(): raise ReleaseCheckError( "installed adopted-lockout route published a bootstrap" @@ -561,6 +623,22 @@ def check_candidate(candidate: Path, expected_tag: str) -> None: raise ReleaseCheckError( "installed inspect route changed target bytes" ) + inspect_general_result = run( + [ + str(command), "inspect", "--project-root", str(adopted), + "--role", "general", + ], + cwd=workspace, + environment=environment, + label="installed inspect general route", + ) + verify_authorization_continuity_content( + inspect_general_result.stdout, "installed inspect --role general output" + ) + if tree_digest(adopted) != adopted_before: + raise ReleaseCheckError( + "installed inspect general route changed target bytes" + ) retired = workspace / "retired-project" retired_governance = retired / "governance" @@ -717,6 +795,9 @@ def check_candidate(candidate: Path, expected_tag: str) -> None: print(" path fails closed; zero target-byte change") print(" nested worktree : installed coordinator stops with a worktree diagnostic") print(" candidate unchanged: complete-tree digest preserved") + print(" authorization contract: GENERAL/OPERATOR/REPOSITORY-OPERATOR, the " + "external Operator packet, and General inspect output all carry the " + "required authorization-continuity labels and outcome sentences") def parser() -> argparse.ArgumentParser: diff --git a/governance/LOG-denials-probes.md b/governance/LOG-denials-probes.md index 8a18451..c31fe5c 100644 --- a/governance/LOG-denials-probes.md +++ b/governance/LOG-denials-probes.md @@ -1402,3 +1402,21 @@ log SHA-256 is `AE6485A2353183917397219801F8443871FE6ED59F0C3CEE27F3D8B6EAE11D85`. No denied mutation succeeded; the authorized coordinator later performed the remote relink outside the Implementer's active grant. + +## WO-WW-027 session-local evidence — 2026-09-14 (post-pilot) + +Session `b6a02b70-972b-4bbd-a27e-c7d020270901`, native Windows Sonnet: + +| Records | Classification | Accounting | +|---|---|---| +| 330-333 | Four Bash work attempts, `control_plane_channel_uninspectable` | Real 9.2.1 post-pilot denials; not relabeled probes | +| 334 | First Write to the authorized excluded canary, `write_target_out_of_grant` | Deliberate canary, excluded from ordinary-denial count | + +The canary target remains absent. The original 329 records (115233 bytes) +retain SHA-256 `5dba70f7d7263ecd10ecb2c18867b6c2c5870a3e001c25c7797d3d61feae680a`. +The 334-record log (117147 bytes) hashes +`2511dabaa26521cf6d07ac04e8a5886348e8ef1cb6d64ba46598f97f89eacdcb`. +Provider tool-event order attributes one append to the canary; no separate +333-record snapshot was captured. Forensics were coordinator-verified, not +computed by the Implementer whose shell calls were denied. No log bytes were +rewritten and no whole-surface enforcement claim changes. diff --git a/governance/LOG-denials.jsonl b/governance/LOG-denials.jsonl index e7aa2d2..feec280 100644 --- a/governance/LOG-denials.jsonl +++ b/governance/LOG-denials.jsonl @@ -327,3 +327,8 @@ {"schema":1,"timestamp":"2026-09-03T16:16:37Z","session_id":"cc17d072-1275-4f14-9edb-c9fc2c9866c1","tool":"Glob","surface":"filesystem.read","work_order":"governance/work-orders/WO-WW-020-canonical-project-root-enforcement.md","decision":"deny","reason_code":"read_traversal_denied","reason":"Read traversal could reach a grant.filesystem.read.deny subtree."} {"schema":1,"timestamp":"2026-09-03T16:18:05Z","session_id":"cc17d072-1275-4f14-9edb-c9fc2c9866c1","tool":"Bash","surface":"shell.execute","work_order":"governance/work-orders/WO-WW-020-canonical-project-root-enforcement.md","decision":"deny","reason_code":"control_plane_channel_uninspectable","reason":"Mutation-capable channel cannot prove protected control-plane targets remain unchanged."} {"schema":1,"timestamp":"2026-09-03T17:47:14Z","session_id":"a3f59b20-0c4d-47bf-b134-02896bb085d7","tool":"Write","surface":"filesystem.write","work_order":"governance/work-orders/WO-WW-021-conversation-first-inception-and-existing-project-continuity.md","decision":"deny","reason_code":"write_target_out_of_grant","reason":"Write target is outside grant.filesystem.write."} +{"schema":1,"timestamp":"2026-09-14T19:55:00Z","session_id":"b6a02b70-972b-4bbd-a27e-c7d020270901","tool":"Bash","surface":"shell.execute","work_order":"governance/work-orders/WO-WW-027-scoped-authorization-continuity.md","decision":"deny","reason_code":"control_plane_channel_uninspectable","reason":"Mutation-capable channel cannot prove protected control-plane targets remain unchanged."} +{"schema":1,"timestamp":"2026-09-14T19:56:54Z","session_id":"b6a02b70-972b-4bbd-a27e-c7d020270901","tool":"Bash","surface":"shell.execute","work_order":"governance/work-orders/WO-WW-027-scoped-authorization-continuity.md","decision":"deny","reason_code":"control_plane_channel_uninspectable","reason":"Mutation-capable channel cannot prove protected control-plane targets remain unchanged."} +{"schema":1,"timestamp":"2026-09-14T19:56:59Z","session_id":"b6a02b70-972b-4bbd-a27e-c7d020270901","tool":"Bash","surface":"shell.execute","work_order":"governance/work-orders/WO-WW-027-scoped-authorization-continuity.md","decision":"deny","reason_code":"control_plane_channel_uninspectable","reason":"Mutation-capable channel cannot prove protected control-plane targets remain unchanged."} +{"schema":1,"timestamp":"2026-09-14T19:57:03Z","session_id":"b6a02b70-972b-4bbd-a27e-c7d020270901","tool":"Bash","surface":"shell.execute","work_order":"governance/work-orders/WO-WW-027-scoped-authorization-continuity.md","decision":"deny","reason_code":"control_plane_channel_uninspectable","reason":"Mutation-capable channel cannot prove protected control-plane targets remain unchanged."} +{"schema":1,"timestamp":"2026-09-14T19:57:16Z","session_id":"b6a02b70-972b-4bbd-a27e-c7d020270901","tool":"Write","surface":"filesystem.write","work_order":"governance/work-orders/WO-WW-027-scoped-authorization-continuity.md","decision":"deny","reason_code":"write_target_out_of_grant","reason":"Write target is outside grant.filesystem.write."} diff --git a/governance/LOG.md b/governance/LOG.md index 9053cf9..889ae38 100644 --- a/governance/LOG.md +++ b/governance/LOG.md @@ -1075,6 +1075,39 @@ whitespace gates passed on the same final record bytes. ## Column definitions +### Post-pilot WO-WW-027 completed record — 2026-09-14 + +Owner ACCEPT including disclosed deviations; active minutes NOT REPORTED. +The original ten-order pilot totals are unchanged. + +- 9.2.1: four real work-attempt Bash denials (330-333), zero successful denied + mutations. Record 334 is the excluded first-Write canary, not ordinary work. +- 9.2.2: zero numbered RFIs opened. Public issue 35 is the existing scope; + issues 36-38 are proposals, not activated successors. +- 9.2.3: aggregate drift count NOT MEASURED. The report retains initial REDs, + failed first GREEN, record corrections, late routing reads and read-boundary + departure; no retroactive numeric drift total is invented. +- 9.2.4: zero formal Reviewer-return rework cycles; one native Sonnet + implementation session and one distinct native Sonnet Reviewer session. + Continuations reused those IDs. Coordinator orchestration and record + corrections are disclosed, not disguised as new independent reviews. + Zero closed artifacts retrieved to reconstruct intent. +- 9.2.5: activation corpus/gap/orphan measurements NOT MEASURED; no reconstruction. +- 9.2.6: 8 declared / 0 wholly enforced / 8 unenforced-by-declaration. Successful + Write-canary denial is channel-local evidence only; closeout is Owner-directed + coordinator recording, not transferable native-wall evidence. +- 9.2.7: mandatory Owner-reading word total NOT MEASURED. The final result and + linked report preceded acceptance; the formal brief is a retrospective + coordinator transcription, not a claim of a separate pre-acceptance brief. +- 9.2.8: N/A for a qualified experimental instrument. Ten synthetic Reviewer + scenarios passed; they establish neither live agent behavior nor cost reduction. + +Final affected suites: Windows 120 tests OK, two symlink-privilege skips; +Ubuntu 120 OK without skips, including real installed-wheel gates. Fresh +Sonnet conformance PASS. Two non-blocking observations remain future candidates, +not new work: explicitly naming pending conditions and maintaining independent +checker expectation strings. No release, push or other-project work is implied. + ### Post-pilot WO-WW-026 completed record Owner ACCEPT on 2026-09-04, including Amendment 1 and disclosed diagnostics; diff --git a/governance/PLAN.md b/governance/PLAN.md index 79e62fa..51c9b39 100644 --- a/governance/PLAN.md +++ b/governance/PLAN.md @@ -844,3 +844,23 @@ Independent implementation and record reviews returned ACCEPT. Owner active minutes: NOT REPORTED. Resume the authorized post-closeout projections, fresh publication review, protected-CI PR/merge, immutable v0.11.0 release, and pilot handoffs; external project mutation is not part of this closeout. + +## 32. Scoped authorization continuity — 2026-09-14 + +Owner ratification: "Approved, proceed", recorded before materialization in +`governance/history/WO-WW-027-issuance-lifecycle.md` (private governed-source reference, not present in this candidate). + +Queue one bounded correction to existing approval-continuity guidance: preserve +and expose the source, limits and delegation scope of an already-approved +action in generated handoffs. Distinguish missing Owner decisions from provider +permission failures. Do not create an authority service, expand delegation, +amend Doctrine, or promise mechanical control over agent conversation. Issues +36-38 remain proposals, not authorized implementation successors. + +**WO-WW-027 COMPLETE, accepted 2026-09-14**, including the disclosed deviations. +Generated handoffs now carry scoped approval evidence and distinguish missing +authority from provider/environment blockers. Native Windows affected gates ran +120 tests with two symlink-privilege skips; Ubuntu ran all 120. Fresh Sonnet +conformance review passed with ten passing synthetic scenarios. No real-world +operating-cost improvement is claimed. Closeout permits one private commit; +issue 35 remains open until a separately authorized public delivery. diff --git a/governance/STATE.md b/governance/STATE.md index 0951865..f7ce24d 100644 --- a/governance/STATE.md +++ b/governance/STATE.md @@ -2,6 +2,34 @@ # STATE — Writwall +## Latest bounded checkpoint — 2026-09-14, WO-WW-027 + +**OBSERVED:** Owner accepted WO-WW-027, including disclosed deviations; active +minutes NOT REPORTED. Approval-continuity source, installed-output checks and +three static handoffs are synchronized. Windows affected suites: 120 tests, OK, +two symlink-privilege skips; Ubuntu: 120 tests, OK, no skips. Fresh read-only +Sonnet conformance review: PASS; ten synthetic scenarios passed. The seven-file +implementation/test/documentation ledger remained unchanged through final review. + +Denial records 330-333 are four real Bash work-attempt denials; 334 is the excluded +Write canary. No denied mutation succeeded; the original 329-record byte prefix +is intact. All eight declared surfaces remain unenforced under the strict metric. +Setup, routing/read-boundary and wrapper deviations remain in the accepted report. + +This is a post-pilot order, not an eleventh counted pilot item. Its work order, +report, lifecycle and closeout brief are retained in `governance/history/`. +Ordinary closeout and one private commit are authorized. Public delivery of this +change is not yet performed. The active pointer is absent; live work-order and +report directories contain only their .gitkeep files. The accepted report was +retired byte-identically. No successor is active. Public delivery of this +change and issue 35 closure are not authorized; issues 36-38 remain proposals. +No external project is changed. No new interpretation or empirical cost claim +is added. Older release/issue status passages below retain their dated snapshot +context and are not a current verification of GitHub state. + +Evidence: `governance/history/WO-WW-027-report.md` (private governed-source reference, not present in this candidate) and +`governance/history/WO-WW-027-issuance-lifecycle.md` (private governed-source reference, not present in this candidate). + As-is, not should-be. Ratified intent lives in `governance/PLAN.md`; this file records the repository state observed after the Plumbline 0.8 public release, accepted WO-PL-034 public-front-door polish, accepted WO-PL-035 onboarding diff --git a/identity/legacy-references.json b/identity/legacy-references.json index 4736ead..042d724 100644 --- a/identity/legacy-references.json +++ b/identity/legacy-references.json @@ -94,19 +94,19 @@ { "path": "governance/LOG.md", "context": "historical_pilot_summary", - "sha256": "ffa9029e76fbe660f9c4eced42b11960bfd96ef0451f7d18e874c349de264aba", + "sha256": "296a97522e331edb71f53cbae4e86bc35560df501e4a020eb1547899fe29a3b2", "projection_transform": "private_evidence_redaction" }, { "path": "governance/PLAN.md", "context": "ratified_historical_intent", - "sha256": "ed0bdab770d734e83debbecc4d740f95018b39a290946d1941e2bbec09f57ee0" + "sha256": "e27b792bf3e315ef24f97a35b7463fd788de6692657b123384f62d5c720f64d2" }, { "path": "governance/STATE.md", "context": "mixed_current_state_and_history", - "sha256": "bff2aba6ff42a6943f0571aabe8f60d8e4b59d681c069cafcc3dd7d7fd296e45", - "projection_sha256": "646887c40607a97b418ec8f09be1c94c51f7eb053bca08908441007a397d31c7" + "sha256": "003319f2acd3b058a2af2601bee35a62423a26a57390de08955f3c859c770011", + "projection_sha256": "ab7aaa8c93e41907b1c7e5767c5b2fcbbd1c15471cd747f54e9b695b25da283a" }, { "path": "governance/decisions/DR-001.md", diff --git a/scripts/start_writwall.py b/scripts/start_writwall.py index 7522fba..1ce359b 100644 --- a/scripts/start_writwall.py +++ b/scripts/start_writwall.py @@ -31,7 +31,69 @@ "record values, or other secrets. This tool writes your answers as plain " "text inside the target project." ) -GENERAL_PROMPT = """Act as a fresh General for this already-adopted project's continuity. Begin +_AUTHORIZATION_UNKNOWN = "unknown: not yet transcribed from an already-authorized record" + + +def authorization_continuity_block( + *, + approval_source: str | None = None, + approved_action: str | None = None, + exact_scope: str | None = None, + exclusions: str | None = None, + delegation_permission: str | None = None, + lifecycle_conditions: str | None = None, + completion_boundary: str | None = None, +) -> str: + """Render the one shared Authorization section for every generated handoff. + + A field carries an already-authorized record's own reference and wording + when the preparer supplies one, including an equivalent legacy record's + existing scope and stated authority transcribed verbatim; the human Owner + never retypes or re-approves it. A field left unset renders as explicitly + unknown rather than a fabricated value: that means the preparer has not + yet located it in an already-authorized current record, not that no such + record exists, and the preparer inspects those records before asking + anyone. Only a field genuinely missing from every authorized record is a + question for the Owner; a blank field alone never invalidates existing + adoption or authority. This function performs no natural-language + parsing; a caller supplies each value only from a record it has already + read. + """ + fields = ( + ("Approval source/reference", approval_source), + ("Approved action", approved_action), + ("Exact scope", exact_scope), + ("Exclusions", exclusions), + ("Delegation permission", delegation_permission), + ("Lifecycle conditions", lifecycle_conditions), + ("Completion boundary", completion_boundary), + ) + lines = "\n".join( + f"- {label}: {value if value else _AUTHORIZATION_UNKNOWN}" + for label, value in fields + ) + return f"""## Authorization + +{lines} + +This section carries forward evidence of a decision already made elsewhere; +it is not itself a decision, and it never substitutes for an independent +provider authorization. A field populated above transcribes that +already-authorized record's own reference and wording; the human Owner never +retypes or re-approves it. A field left unknown above means the preparer has +not yet located it in an already-authorized current record; the preparer +inspects those records before asking anyone. Only a genuinely missing, materially necessary decision is a question for the Owner; the absence of optional or formal metadata is not itself an approval loop, and an existing valid legacy approval remains usable without new paperwork. + +Matching current approval: performs the already-authorized action once the provider itself permits it. +Missing approval: says plainly that authorization is missing and stops. +Explicit revocation or supersession: treats a revoked or superseded record as no longer authorizing anything. +Requested action beyond scope: performs only the authorized part and names the excess as unauthorized. +Independent provider denial: reports the provider's own denial as the exact blocker. +Environment prerequisite failure: names the exact missing or failed environment prerequisite as the blocker. +Unapproved task creation or data transmission: never creates or transmits a task, message, or dataset outside the approved action.""" + + +GENERAL_PROMPT = f"""Act as a fresh General for this already-adopted project's continuity. Begin read-only and verify the lifecycle from repository bytes rather than prior chat. Read the charter, Plan, State, Routing, ratified adoption record, and open transactional records. State the project's next decision plainly. Prepare, but do not activate, the smallest genuine work @@ -41,7 +103,13 @@ supporting evidence rather than the conversational front door. When the next safe mechanical action is available, ask once for one combined disposition and action. If that action uses a new user-owned task, explicitly include creation and dispatch of the named task in that approval -request; never infer task-creation permission afterward. Once approved, perform every +request; never infer task-creation permission afterward. Carry that approval's continuity in the +shared Authorization section below, transcribed from an already-authorized current record rather +than retyped or re-approved by the Owner. + +{authorization_continuity_block()} + +Once approved, perform every mechanically available authorized step. Do not ask for the same decision again. The human Owner alone ratifies intent and activates work; preserve a distinct fresh Reviewer after implementation. The onboarding coordinator stops here and does not continue into project work.""" @@ -682,6 +750,8 @@ def operation_packet(function_name: str, canonical: str) -> str: executes only the completed packet and returns evidence. {_external_operator_root_block(canonical)} +{authorization_continuity_block()} + ## Preconditions - [ ] Identify the exact system, account boundary, and observed baseline. @@ -1347,6 +1417,8 @@ def architect_packets( {common} {root_block} +{authorization_continuity_block()} + ## Preconditions - An Owner-ratified plan and active bounded work order exist. ## Permitted actions @@ -1387,6 +1459,8 @@ def architect_packets( integrations should read `OPERATOR.md` directly; both describe the same Operator role. +{authorization_continuity_block()} + ## Preconditions - An Owner-ratified plan and active bounded work order exist. ## Permitted actions diff --git a/skills/writwall-adopt/SKILL.md b/skills/writwall-adopt/SKILL.md index 28b6fa1..a141000 100644 --- a/skills/writwall-adopt/SKILL.md +++ b/skills/writwall-adopt/SKILL.md @@ -270,12 +270,48 @@ with a concise Recommendation and material tradeoff; keep the detailed packet be supporting evidence rather than the conversational front door. When the next safe mechanical action is available, ask once for one combined disposition and action. If that action uses a new user-owned task, explicitly include creation and dispatch of the named task in that approval -request; never infer task-creation permission afterward. Once approved, perform every +request; never infer task-creation permission afterward. Carry that approval's continuity in the +shared Authorization section below, transcribed from an already-authorized current record rather +than retyped or re-approved by the Owner. + +## Authorization + +- Approval source/reference: unknown: not yet transcribed from an already-authorized record +- Approved action: unknown: not yet transcribed from an already-authorized record +- Exact scope: unknown: not yet transcribed from an already-authorized record +- Exclusions: unknown: not yet transcribed from an already-authorized record +- Delegation permission: unknown: not yet transcribed from an already-authorized record +- Lifecycle conditions: unknown: not yet transcribed from an already-authorized record +- Completion boundary: unknown: not yet transcribed from an already-authorized record + +This section carries forward evidence of a decision already made elsewhere; +it is not itself a decision, and it never substitutes for an independent +provider authorization. A field populated above transcribes that +already-authorized record's own reference and wording; the human Owner never +retypes or re-approves it. A field left unknown above means the preparer has +not yet located it in an already-authorized current record; the preparer +inspects those records before asking anyone. Only a genuinely missing, materially necessary decision is a question for the Owner; the absence of optional or formal metadata is not itself an approval loop, and an existing valid legacy approval remains usable without new paperwork. + +Matching current approval: performs the already-authorized action once the provider itself permits it. +Missing approval: says plainly that authorization is missing and stops. +Explicit revocation or supersession: treats a revoked or superseded record as no longer authorizing anything. +Requested action beyond scope: performs only the authorized part and names the excess as unauthorized. +Independent provider denial: reports the provider's own denial as the exact blocker. +Environment prerequisite failure: names the exact missing or failed environment prerequisite as the blocker. +Unapproved task creation or data transmission: never creates or transmits a task, message, or dataset outside the approved action. + +Once approved, perform every mechanically available authorized step. Do not ask for the same decision again. The human Owner alone ratifies intent and activates work; preserve a distinct fresh Reviewer after implementation. The onboarding coordinator stops here and does not continue into project work. ``` +The Authorization section above is filled in by the General itself from +already-approved current records, or an equivalent legacy record's existing +scope and authority; the Owner is never asked to retype or re-approve values +that already exist, and a blank field alone is not a new approval service or +a performance claim. + ## Migration mode If the Owner states the repository was bootstrapped under an earlier doctrine revision, look for `references/migration-guides/-to-.md` in this bundle and follow it instead of treating prior artifacts as unknowns. This bundle ships the 0.1-to-0.6 and 0.6-to-0.7 guides. Each requires the project's Owner to explicitly ratify migration before it is followed; neither runs on your own initiative. If no guide for the stated transition is bundled, say so, treat prior artifacts as Phase A inventory items, and propose dispositions; do not guess at what the earlier revision meant. diff --git a/tests/test_coordinator_release.py b/tests/test_coordinator_release.py index 6e029d1..6be85ff 100644 --- a/tests/test_coordinator_release.py +++ b/tests/test_coordinator_release.py @@ -215,6 +215,60 @@ def test_installed_reintroduced_filename_only_adoption_defect_fails_release_gate result.stdout + result.stderr, ) + def test_installed_release_gate_catches_missing_authorization_continuity_label(self): + candidate = self.make_candidate() + start = candidate / "scripts" / "start_writwall.py" + original = start.read_text(encoding="utf-8") + self.assertIn( + '("Approval source/reference", approval_source),', original + ) + start.write_text( + original.replace( + '("Approval source/reference", approval_source),', + '("Approval source", approval_source),', + ), + encoding="utf-8", + newline="\n", + ) + before = tree_digest(candidate) + result = self.run_checker(candidate) + self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn( + "authorization-continuity content missing", + result.stdout + result.stderr, + ) + self.assertIn("Approval source/reference", result.stdout + result.stderr) + self.assertEqual(tree_digest(candidate), before) + + def test_installed_release_gate_catches_missing_authorization_outcome_guidance(self): + candidate = self.make_candidate() + start = candidate / "scripts" / "start_writwall.py" + original = start.read_text(encoding="utf-8") + self.assertIn( + "Missing approval: says plainly that authorization is missing and stops.", + original, + ) + start.write_text( + original.replace( + "Missing approval: says plainly that authorization is missing and stops.", + "Missing approval: proceeds anyway.", + ), + encoding="utf-8", + newline="\n", + ) + before = tree_digest(candidate) + result = self.run_checker(candidate) + self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn( + "authorization-continuity content missing", + result.stdout + result.stderr, + ) + self.assertIn( + "says plainly that authorization is missing and stops", + result.stdout + result.stderr, + ) + self.assertEqual(tree_digest(candidate), before) + def test_installed_help_mismatch_fails_with_diagnostic(self): candidate = self.make_candidate() entry = candidate / "writwall_cli" / "__main__.py" diff --git a/tests/test_start_writwall.py b/tests/test_start_writwall.py index b4de585..97478c7 100644 --- a/tests/test_start_writwall.py +++ b/tests/test_start_writwall.py @@ -1294,6 +1294,147 @@ def test_external_operators_receive_separate_inert_packets(self): self.assertIn(heading, text) self.assertIn("confers no authority", text) + def test_external_operator_packet_states_authorization_continuity_fields(self): + packet = starter_module.operation_packet("Example function", "/example/project") + self.assertIn("## Authorization", packet) + for label in ( + "Approval source/reference:", + "Approved action:", + "Exact scope:", + "Exclusions:", + "Delegation permission:", + "Lifecycle conditions:", + "Completion boundary:", + ): + self.assertIn(label, packet) + self.assertIn("evidence of a decision", packet) + self.assertIn("not itself a decision", packet) + + def test_repository_role_handoffs_state_authorization_continuity_fields(self): + """Table-driven: the same source/scope/delegation/conditions/completion + contract used by the external Operator packet must also reach + generated repository General and repository-Operator handoffs, + including the zero-write adopted inspect route. Stable field labels + are the emitted interface; prose around them is not asserted.""" + labels = ( + "Approval source/reference:", + "Approved action:", + "Exact scope:", + "Exclusions:", + "Delegation permission:", + "Lifecycle conditions:", + "Completion boundary:", + ) + + with self.subTest(surface="GENERAL_PROMPT constant"): + for label in labels: + self.assertIn(label, starter_module.GENERAL_PROMPT) + + packets = starter_module.architect_packets( + SimpleNamespace(), (), "/example/project" + ) + for packet_name in ("GENERAL.md", "OPERATOR.md", "REPOSITORY-OPERATOR.md"): + with self.subTest(surface=f"architect_packets {packet_name}"): + for label in labels: + self.assertIn(label, packets[packet_name]) + + with self.subTest(surface="adopted zero-write inspect route (general role)"): + governance = self.project / "governance" + governance.mkdir() + for name in ("PLAN.md", "STATE.md", "ROUTING.md"): + (governance / name).write_text(f"# {name}\n", encoding="utf-8") + decision = governance / "decisions" / "DR-001.md" + decision.parent.mkdir() + decision.write_text(ratified_adoption_record(), encoding="utf-8") + before = self.tree_snapshot(self.project) + result = self.run_inspect("general") + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(self.tree_snapshot(self.project), before) + for label in labels: + self.assertIn(label, result.stdout) + + def test_authorization_continuity_block_preserves_supplied_values_without_inventing_others(self): + block = starter_module.authorization_continuity_block( + approval_source="governance/decisions/DR-005.md", + approved_action="Publish the v0.11.0 release archive.", + ) + self.assertIn( + "Approval source/reference: governance/decisions/DR-005.md", block + ) + self.assertIn( + "Approved action: Publish the v0.11.0 release archive.", block + ) + for label in ( + "Exact scope", "Exclusions", "Delegation permission", + "Lifecycle conditions", "Completion boundary", + ): + self.assertIn( + f"{label}: {starter_module._AUTHORIZATION_UNKNOWN}", block + ) + + def test_generated_guidance_covers_b34_authorization_outcomes(self): + """Table-driven: repository General guidance and the external Operator + packet must each give distinguishing instruction for every B.3.4 + authorization outcome, not a generic keyword shared across cases. + This is prose guidance for a reasoning agent, not a claim that the + generator itself parses or validates arbitrary prose decisions.""" + cases = ( + ( + "matching current approval", + "performs the already-authorized action once the provider itself permits it", + ), + ( + "missing approval", + "says plainly that authorization is missing and stops", + ), + ( + "explicit revocation or supersession", + "treats a revoked or superseded record as no longer authorizing anything", + ), + ( + "requested action beyond scope", + "performs only the authorized part and names the excess as unauthorized", + ), + ( + "independent provider denial", + "reports the provider's own denial as the exact blocker", + ), + ( + "environment prerequisite failure", + "names the exact missing or failed environment prerequisite as the blocker", + ), + ( + "unapproved task creation or data transmission", + "never creates or transmits a task, message, or dataset outside the approved action", + ), + ) + surfaces = { + "GENERAL_PROMPT": starter_module.GENERAL_PROMPT, + "external operation_packet": starter_module.operation_packet( + "Example function", "/example/project" + ), + } + for surface_name, text in surfaces.items(): + for outcome, expected_guidance in cases: + with self.subTest(surface=surface_name, outcome=outcome): + self.assertIn(expected_guidance, text) + + def test_static_general_prompt_docs_stay_normalized_equal_to_generated_prompt(self): + """Retrospective regression (WO-WW-027): the three static GENERAL_PROMPT + copies (START-HERE.md, ADOPTING.md, skills/writwall-adopt/SKILL.md) must + each carry the exact generated prompt text, whitespace-normalized, so a + future change to the shared renderer cannot silently desync the docs. + This does not weaken or alter GENERAL_PROMPT itself; it only pins the + static copies to whatever it currently says.""" + normalized_prompt = " ".join(starter_module.GENERAL_PROMPT.split()) + for relative in ( + "START-HERE.md", "ADOPTING.md", "skills/writwall-adopt/SKILL.md", + ): + with self.subTest(document=relative): + text = (REPO_ROOT / relative).read_text(encoding="utf-8") + normalized_text = " ".join(text.split()) + self.assertIn(normalized_prompt, normalized_text) + def test_dns_mail_scenario_is_split_without_real_values(self): result = self.run_start("--scenario", "dns-mail-migration") self.assertEqual(result.returncode, 0, result.stdout + result.stderr)