diff --git a/ADOPTING.md b/ADOPTING.md index 52a9721..ae6f154 100644 --- a/ADOPTING.md +++ b/ADOPTING.md @@ -42,9 +42,13 @@ locked session may deny the network request that would otherwise retrieve them. For a first adoption, the default is the day-zero coordinator: +Release candidate `v0.9.3` contains the terminal Architect handoff described +below but is not yet published. The tagged install command becomes valid only +after that release is published; until then, use the checked source-tree fallback. + ```text -# Install the current tagged release. -python -m pip install "https://github.com/HLLMR/writwall/archive/refs/tags/v0.9.2.zip" +# After v0.9.3 is published, install that tagged release. +python -m pip install "https://github.com/HLLMR/writwall/archive/refs/tags/v0.9.3.zip" # Installed command writwall start --project-root /path/to/your-project @@ -57,11 +61,13 @@ python3 scripts/start_writwall.py --project-root /path/to/your-project ``` It classifies the target from repository bytes, copies the complete skill bundle -into a temporary `.writwall-bootstrap/` directory, and emits the exact next -prompt. It is create-only bootstrap tooling, not an authority or installer. -It also initializes a durable project-specific privacy screen in per-user local -state outside the repository. The handoff records only ready status and entry -count, never its location or contents. See +into a temporary `.writwall-bootstrap/` directory only for a clean/new target, +and emits the exact next prompt. It is lifecycle-aware bootstrap and routing +tooling, not an authority or installer. Non-clean valid states skip intake and +change no target bytes. Only clean/new mode initializes a durable project- +specific privacy screen in per-user local state outside the repository. The +bootstrap handoff records only ready status and entry count, never its location +or contents. Its clean/new branch remains create-only bootstrap tooling. See [`docs/privacy-screen.md`](docs/privacy-screen.md). Contradictory active state stops before output. Full interface and external- Operator packet behavior are documented in @@ -80,6 +86,14 @@ Routes combine. A common path for an existing project is A (mapping conversation The coordinator selects among these routes; it does not replace them. Its handoff is temporary and must be removed before the adoption commit. +| Human command | Observed state | Fresh role receiving output | Prior session stops | Target bytes | +|---|---|---|---|---| +| `writwall start --project-root ` | Clean/new | Adoption coordinator | Launcher returns; coordinator stops at adoption closeout | Create-only bootstrap may be added | +| Same command | Partial/recovery | Recovery coordinator | Incomplete or locked session | Unchanged | +| Same command | Adopted/retired lockout | Owner-Agent / Project-Architect | Onboarding or prior work session | Unchanged | +| Same command | Active work order | Bounded Implementer | Prior coordinator or Implementer context | Unchanged | +| Same command | Malformed/contradictory | No role; fail-closed diagnostic | Invoking session | Unchanged | + --- ## 2. Route A: the adoption prompt @@ -281,12 +295,41 @@ What counts as proof is a **live-wall canary**: an Owner-authorized, genuinely m 5.9 Make the adoption commit containing `governance/`, the charter, the adapter, and DR-001. Its message names the baseline hash. This is one local commit; it is not a push, a tag, or a release. You may make it yourself or have an authorized recorder make it on your behalf after you have ratified exactly what it will contain — in either case the commit records a decision that was already yours. -5.10 Dispatch WO-001 as whatever the project genuinely needs next (6.1.4). -After the candidate passes `--work-order`, create -`.claude/active-wo.txt` with exactly one LF-terminated, repository-relative -line naming it, then run `--active`. The pilot begins. This is a fresh Owner -decision after adoption, never a continuation of the closeout: no bootstrap -or recorder run starts it. +5.10 End onboarding and hand the adopted project to a fresh Owner-Agent / +Project-Architect. The recorder presents this exact prompt and stops: + +```text +Act as a fresh Owner-Agent / Project-Architect. 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. Draft, but do not activate or +implement, the smallest genuine work order or bounded external Operator packet. +Lead with a concise Recommendation and material tradeoff; keep the detailed +packet behind it as 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 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 review after implementation. The onboarding coordinator stops here and +does not continue into project work. +``` + +The Architect drafts 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; +the complete packet is supporting evidence. When a safe next mechanical action +is available, its single approval request combines the disposition with that +action. A new user-owned task is created or dispatched only when that same +request explicitly asks for it. After approval, the Architect performs all +mechanically available authorized steps without asking for the decision again. + +For a repository work order, activation remains a distinct Owner decision. +After the candidate passes `--work-order`, the Owner-authorized lifecycle +creates `.claude/active-wo.txt` with exactly one LF-terminated, repository- +relative line naming it, then runs `--active`. The pilot begins. No bootstrap +or recorder run activates or implements that work. --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 777d5d9..8ab1fb2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,7 +78,7 @@ gate against the final checked public candidate on native Windows and native Ubuntu: ```text -python checks/check_coordinator_release.py --expected-tag v0.9.2 +python checks/check_coordinator_release.py --expected-tag v0.9.3 ``` The gate copies the candidate to temporary build space, builds and installs the diff --git a/PROJECTION-MANIFEST.sha256 b/PROJECTION-MANIFEST.sha256 index 81f77ff..71b7ae6 100644 --- a/PROJECTION-MANIFEST.sha256 +++ b/PROJECTION-MANIFEST.sha256 @@ -5,9 +5,9 @@ b2e36dfcfc6eb31570c9340640bcd73abc62f57c80b4794abf36e3d3e89ab34f .github/depend 9e90d43615b02a265b08692ef7a1c00a37477a5c6b1e8233a8fc7bedefaedea6 .github/pull_request_template.md 40e5cddf710e4e6fa4984ac864fdb30a7031005ef1a80082ce0cc6a4f9bc115e .github/workflows/ci.yml e544abe8ffd83c81c7b002cbd2e552f9d56f226ea20e1e0722c5d1bdec914fe0 .gitignore -73bdedc8e39d50d9ee61e776583e601797b6e2a2b6c50d2741959472eb072861 ADOPTING.md +3070e0fcfea3c39c621d323b586e108ee86e83f56d4300117bba45db0f49124b ADOPTING.md 1179c999034f4ec1c1d44c1946bd2955c4625905e80767abe760c8c3ab01c493 CLAUDE.md -ab361087e7fd15a160587426f7ac6e8d45e7546fc821c2b07432a8e007447afa CONTRIBUTING.md +86f3193d5174d8bc5a594a2c5a065de97255e9f223da374632c8eb3f592ca6ab CONTRIBUTING.md 664196054cd98585105be457afa09c788a482416ccb48a87bb269b2156e49ae6 DOCTRINE.md 9ba9550ad48438d0836ddab3da480b3b69ffa0aac7b7878b5a0039e7ab429411 LICENSE a38775f2d68b40577253ee48061ba67af3c75b7620dc506b34b40ce2a3b660ee LICENSE-MAP.md @@ -16,17 +16,17 @@ c274f80372d90c012937370f0e1f15087d22e308ef98b27cea5dc0d2d088366c LICENSES/Apach a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499 LICENSES/CC0-1.0.txt 59746d6285ffa44bfc7ecada352aa5d6a20dc8eab418a60ce091cc739012c135 LICENSES/MIT-0.txt 35e6d37b7c5fa0c1fc872315cbd362cd24bfa41e1b7dc3019fbcd31e99350f51 NAMING.md -bbbc4ba483ace56bc7e3827cdc24b867db981efec5ba13d53e1b788591d430ee PROJECTION-PROVENANCE.md -96b3466c452f1288020b5f62e0729deb04c860e5bd0d34a3f5f2418ee9aa25f0 PUBLICATION.md -e81903350f305351ccd52e7acaa584b4683b7ba65fa1ecbeb2c048eec357e483 README.md +61e2afa6304c7e1ba8f2cd9687d51d8bb9ab5f9c9b68bb6883b1f4252288ff51 PROJECTION-PROVENANCE.md +9716fb18ddca4626791e17474cdba2fd343f47886080c6ff8b1e7c41f63830ec PUBLICATION.md +2bef9b65ca9ff3aec8dc8149f185f337c7f0e4d71c79c62661f37f9c1a543c82 README.md 284a0862f3be77e8d867aa4d3ef92ed1a64ad4315d6d9074f6bb64771b6d1dd0 REUSE.toml ab75b39490b4db4e203f5b23b480a1c998d87cf07d760cb787cb260778b21d0a SECURITY.md 6a51c1211cc675599634d144ca24a705ec1696f84640a6464b14efb1d6c3a629 SELF-HOSTING.md -04ee91f398218f895f9ef4cb990c01e3fea1847e46882a7700f352d00e879bda START-HERE.md +10d0bc5c620e4fb108869027cb69bd754e616927b9f9e6cec635a02b43abce1d START-HERE.md 75c7ae0f569148f489570d63df916a70b6ccf24b77db2076cdee29663f747428 adapters/claude-code/README.md aeb7f81d139e7ffa6de9a1782444b99eb6d549ac1c3a8b9c0f8bcb9c6addfa6d adapters/claude-code/SECURITY.md dd29af2a39d25e0270ad9acc23ee912f81e39c674e1179759f4a3010c6a0c1a0 adapters/claude-code/wo_capability_wall.py -3663baaaede31c20b87cbe27b37a7a2db6485782ffc581d2e3e05ca525adade7 checks/check_coordinator_release.py +cb1647439902b2ee7546ed7c7c84375f01164b662047e99f42002f5e5a8335bf checks/check_coordinator_release.py b872c46f8a8ea2fc317b4c4fd0af00c9145012b11256c923987d878d79c726c2 checks/check_distribution.py 60fe377dac32b8d1697f859371ef40d26ed4e695fdceeda6d29ec0318d539504 checks/check_identity.py 30986c40ff7c9b29e2fba39ec04c18af3c1c351490410bd532a8391bcb92ed11 checks/check_licenses.py @@ -48,7 +48,7 @@ fa88788242d920999b6e6737ea60b03dfe3f9ba2e0d90386b6bbcfeb6acd0509 docs/agents/do 0a5259d80265765aee16a421546e44458a1aabeecfa8c7f7dea8aead6a79655b docs/assets/writwall-readme-banner-0a5259d8.png ad0fb4f671b8da9e3ab9720af7b39ac9c93201e6131c1df996e090a2bb2acc8a docs/assets/writwall-readme-banner.svg 7cfd0ae28d07cdfbb367adc4f7e606a1538ebf61ff140a32f8e793028110cedf docs/bootstrap-charter-addendum.md -efd77cef5df06a36f880053d4a33d038766fe9c22d2958b06072a6257d4014ac docs/day-zero-coordinator.md +d5c6554675bd34015949381bce96b7f46309bc27db12a5e583f3f4b2f9c2e2ca docs/day-zero-coordinator.md e1214e3e6018642809339249bb091a6fd754847b4f77c4bc7a39c5c87e6769cc docs/identity-migration.md 4d54cd53db8c165b40af7eb11b97a7f4faf5f23479c0efc8238e5d463b159008 docs/name-clearance.md 1eef400dd2e12b109ceb9b30c107dbd7f25d3c64182346dd0f370bf9ccaa7088 docs/privacy-screen.md @@ -62,10 +62,10 @@ d2c5a8ca21edf842dfd17a83862024afa0a92349abf693a60e55ce454c8d78fa examples/name- 30cdb11fbeb2fd9bbf4048255331ccbbdd6e516fae5adfa5317af00ce53607c9 governance/ADOPTION-MAPPING.md 08b235351ab7799715b1e2df4fa3dd9fa88bb85084c8aade4d01039bf255b489 governance/LOG-denials-probes.md 50784b173c90c4fd22572306429187c8a7e22614f4b9ad649a3005647467c7fa governance/LOG-denials.jsonl -6cd8bb10ebd50071b2f355c1934fdbb525452dabeee7cc98f24eaddf03a1fef6 governance/LOG.md -fcbdd620daea8b69e35ecb859f08f5332ceb89f6ac5dc9a293096b4190e4d437 governance/PLAN.md +8f1ae9b4f005a31eed74e9f68429fed33fc6ffed2bd31e05de59327d4f9db220 governance/LOG.md +3b5645777ed304ce7205fbd797323320fd10ddd421bf4b9562c5bd3c28b4df9f governance/PLAN.md dd445eb2994e0d9615bc61fe2ae157a6b14ba7b190c65b705d380b31c49fdfe0 governance/ROUTING.md -609d2afa9e0bb198caebbfc08aee0b541fd713041b106d66100b5670bdf9639a governance/STATE.md +abd6676fd83bb1e7a480048ffc3e717fe9184a41e5c887ef223c40d3bed113a1 governance/STATE.md e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/archive/.gitkeep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/briefs/.gitkeep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/decisions/.gitkeep @@ -82,20 +82,20 @@ b567ce0c0867464328e81774d888f6491fa66b68ac73be01f993e5c4c66d3ed8 governance/tem d355e46f978f17de8824af805e05124e0f20b1c072523b518422044f88c6f079 governance/templates/D-adoption-record.md 2b586efadab716a59fcafb74312a45a05401a4787fee6ae18cb5c9dd14ef3a09 governance/templates/E-adoption-mapping.md e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 governance/work-orders/.gitkeep -37cb8ccdbe1fd69854c88cf23acacae830cc14c532392a2a970b28530b9bee11 identity/legacy-references.json +2f0ce5496677f7a85b3bf49c22ebd16e8ece655bf1cc53a8714d1f148142bc0e 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 7be9ff49c33830f929584e9e6756f06be8634b1c79ff7210bf5184b51bfc0769 migration-guides/0.7-to-0.8.md fafcbf659c40d7260dddaa8a64b6b59c3b7de484f77d879a90323bb4b3e1a4a7 projection/public-files.txt -8a70068ac39d532fd783ae50e7f1458175a93dd8a31c3ccde6461d40e28f5430 pyproject.toml +cb875755a70e151f9ea75b11d88da8dff8f5e9732ff923c5d01156ecc392a445 pyproject.toml 455ca1ab3c9e7e78afbb9946e13b94497ba24003ab6f411ea96cce26d4ecc39e scripts/build_distribution.py bdbe47e8ea246ceafb874229d5582a9cc18db7c66250c9873d50a787bd5d9fe2 scripts/build_public_projection.py 3cf88f936599e0e84bc2368bc0503a39b9f96e47b473e09b26569e5c3c9edbd9 scripts/collect_name_clearance.py 8441030e9df498e9c2621d37d9bf77850599723c985b1d958b8d11638dcb1ae4 scripts/privacy_screen.py -9885aa8bf47e055571ce47b35cecb655415d0355113e36bc341397ae38f0e2c9 scripts/start_writwall.py +fb793b5d08981e9a2f44f62c26d79d3652aa56f5d6db600fca4ce5546ba2c141 scripts/start_writwall.py 374f4e8a80b7b9e162b9360a3907b6ffe12ce94ba0ed827058c7b3c9c0658b2c skills/writwall-adopt/LICENSE-MAP.md -deb31382704a7db0531d96d06581a9d27082b9e77ee2ea319c9242d530a78ea4 skills/writwall-adopt/SKILL.md +1c15f2a97cddf727f3173b8971e6ea3e05ab93f57664f8c3031eb0e634a87933 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 -c276150136fc37b056e43700b802ab42b29ecb0123028116e7bda1d6a03df146 tests/test_coordinator_release.py -22901437098bb4883153af4f8d02f14e02585343791ca813aa425e2956510927 tests/test_distribution.py +61c0276a850b38c39c9a8fb355b71fae6f9fa228d8cb664cff8f8c9ca60b1173 tests/test_coordinator_release.py +e5e6df9ead5effa20ee48167f070aa43eb8716e4df66fd997df65d6c2edc7c69 tests/test_distribution.py e150a2f988a4b0beac5f70644f55f5e185a8aa575e988a19642bafabc0f07775 tests/test_identity_migration.py 11cd8090dbc53e8aa6a2f14cb181c8a11696335da8f40700eae5116798e49ba5 tests/test_init_sh.py 96c255d84e37b8884cde769897e763776b81027080c2308c33dc5e4b8df4a4bf tests/test_name_clearance.py a8345b5da77a8b73dd0269110be89bc0ad85c253f2c76b1b075d494fa018a21f tests/test_privacy_screen.py ee771c239c0fc072675f88617dec7e330c1e19be90a8b22691d7049dbb5a4544 tests/test_public_projection.py -5673f51cddbff6f0dc82c11dd3cc581ee2d0457717a5797844c464aff21e62b6 tests/test_start_writwall.py +478a470078cfb0ac10a9e93c5d4c656830bf089f7b7a4999c7ce975b7dc0b08e tests/test_start_writwall.py 0684c04067eb95eadc9f72ab126d8662b4a5e2005c80b2dea174075a6140eebc tests/test_wo_capability_wall.py e8caf7f4421dc7f78b0d766741ec2ef4c2ac6dab6117fab6e4175b27d31e4d49 writwall_cli/__init__.py 9e0fbb2625b7a99ed80a3a708f8a0742d91bbf7e3cd2862d21a75515031083e5 writwall_cli/__main__.py diff --git a/PROJECTION-PROVENANCE.md b/PROJECTION-PROVENANCE.md index f5740c3..62a75e3 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: `0150c3a97996f50a9faf77501c03b7ff58a237a1` -- Source commit time: `2026-09-01T12:20:52-05:00` +- Source commit: `9da1a2b34e787737837a9c857f002dc23ebfb277` +- Source commit time: `2026-09-02T12:10:32-05:00` - Projection allowlist SHA-256: `fafcbf659c40d7260dddaa8a64b6b59c3b7de484f77d879a90323bb4b3e1a4a7` ## Legacy identifier inventory diff --git a/PUBLICATION.md b/PUBLICATION.md index d9f8ae6..085c6b8 100644 --- a/PUBLICATION.md +++ b/PUBLICATION.md @@ -13,7 +13,7 @@ point. Before creating a release tag, run this gate against the final external candidate on native Windows and native Ubuntu, naming the exact intended tag: ```text -python checks/check_coordinator_release.py --expected-tag v0.9.2 +python checks/check_coordinator_release.py --expected-tag v0.9.3 ``` The command fails before building unless the canonical intended tag matches diff --git a/README.md b/README.md index 01c4753..7da7fa4 100644 --- a/README.md +++ b/README.md @@ -93,14 +93,18 @@ actually blocks the current session before real work begins. ## Try it in five minutes -If you are new to Writwall, run the day-zero coordinator from this clean source -distribution. It inspects the target before assigning a role, makes the complete -adoption bundle local, and writes the exact next prompt without installing the -wall or claiming adoption: +Run the lifecycle-aware coordinator from this clean source distribution. It +inspects the target before intake, assigns the correct fresh role, and changes +target bytes only for clean/new bootstrap. It never installs the wall or claims +adoption. This routing happens without installing the wall or claiming adoption: + +Release candidate `v0.9.3` contains the terminal Architect handoff described +below but is not yet published. The tagged install command becomes valid only +after that release is published; until then, use the checked source-tree fallback. ```text -# Install the current tagged release. -python -m pip install "https://github.com/HLLMR/writwall/archive/refs/tags/v0.9.2.zip" +# After v0.9.3 is published, install that tagged release. +python -m pip install "https://github.com/HLLMR/writwall/archive/refs/tags/v0.9.3.zip" # Installed command writwall start --project-root /path/to/your-project @@ -113,11 +117,20 @@ py -3 scripts/start_writwall.py --project-root C:\path\to\your-project python3 scripts/start_writwall.py --project-root /path/to/your-project ``` -The command asks one question at a time, creates `.writwall-bootstrap/` in the -target, and initializes a durable project-specific privacy screen in your -operating system's local user state outside the repository. Read its -`HANDOFF.md`, open the named -agent in the named location, and paste the supplied prompt. Do not enter +| Observed state | Fresh role | Prior session stops | Target bytes | +|---|---|---|---| +| Clean/new | Adoption coordinator | Launcher returns; onboarding later stops at closeout | Create-only bootstrap may be added | +| Partial/recovery | Recovery coordinator | Incomplete or locked session | Unchanged | +| Adopted/retired lockout | Owner-Agent / Project-Architect | Onboarding or prior work session | Unchanged | +| Active work order | Bounded Implementer | Prior coordinator/Implementer context | Unchanged | +| Malformed/contradictory | No role; fail-closed diagnostic | Invoking session | Unchanged | + +For clean/new state, the command asks one question at a time, creates +`.writwall-bootstrap/`, and initializes a durable project-specific privacy +screen in your operating system's local user state outside the repository. +Read its `HANDOFF.md`, open the named agent in the named location, and paste the +supplied prompt. Later states print their fresh-role handoff directly without +intake, privacy initialization, or target writes. Do not enter passwords, API tokens, private keys, mailbox contents, DNS values, or other secrets; intake is stored as local plain text. See the [coordinator reference](docs/day-zero-coordinator.md), or use diff --git a/START-HERE.md b/START-HERE.md index ba5fda4..7f60ba8 100644 --- a/START-HERE.md +++ b/START-HERE.md @@ -59,16 +59,18 @@ names, repository slugs, domains, logos, or launch copy. The coordinator may collect evidence, but the Owner chooses the identity; unavailable sources are not clear results. -1. Install the current public coordinator without unpacking it over your - project: +1. Release candidate `v0.9.3` contains the terminal Architect handoff but is + not yet published. After publication, install it without unpacking it over + your project; until then, use the checked source-tree fallback below: ```text - python -m pip install "https://github.com/HLLMR/writwall/archive/refs/tags/v0.9.2.zip" + python -m pip install "https://github.com/HLLMR/writwall/archive/refs/tags/v0.9.3.zip" ``` Release `v0.9.0` first introduced the coordinator. Release `v0.9.1` corrected first-use bytecode residue. Release `v0.9.2` corrects the bootstrap - expected-denial contract and is the minimum recommended coordinator release. + expected-denial contract. Release candidate `v0.9.3` adds lifecycle-aware + routing and the terminal Architect handoff. If you are testing an unpublished release candidate, use its checked external candidate tree and the release gate in `PUBLICATION.md`. 2. Run one command: @@ -84,12 +86,23 @@ not clear results. python3 scripts/start_writwall.py --project-root /path/to/your-project ``` + The same command is the entry point throughout the project lifecycle: + + | Observed state | Fresh role receiving the output | Session that stops | May target bytes change? | + |---|---|---|---| + | Clean/new | Adoption coordinator | The human's current launcher returns after creating the bootstrap; the adoption coordinator later stops at closeout | Yes: create-only `.writwall-bootstrap/` | + | Partial bootstrap or recovery | Recovery coordinator | The incomplete adoption or locked session | No | + | Adopted or retired lockout | Owner-Agent / Project-Architect | The onboarding coordinator or prior work session | No | + | Active work order | Bounded Implementer | Any prior coordinator or Implementer context | No | + | Malformed or contradictory | No role; precise stop diagnostic | The invoking session | No | + 3. If you choose to track Owner active minutes, start the timer when the first question tells you to; do not reconstruct time later. Answer one question at a time without entering secrets. You may point it at an existing brief. The - command observes actual repository lifecycle state, creates - `/.writwall-bootstrap/`, and initializes a durable local privacy - screen outside the repository. Add only private names, codenames, client + command observes actual repository lifecycle state. Only a clean/new target + enters intake, creates `/.writwall-bootstrap/`, and initializes a + durable local privacy screen outside the repository. Later valid states emit + a fresh-role prompt without changing target bytes. Add only private names, codenames, client identifiers, or domains; never add credentials or secret values. See [`docs/privacy-screen.md`](docs/privacy-screen.md). 4. Open its `HANDOFF.md`. Start the agent and location it names and paste the @@ -235,21 +248,41 @@ temporary bootstrap bundle before the adoption commit, return the project to lockout, and do not dispatch WO-001. ``` -## First work order +## Terminal Project-Architect handoff -After the adoption commit, tell the Dispatcher: +Adoption closeout ends the onboarding session. After the adoption commit, open +a fresh Owner-Agent / Project-Architect and paste exactly: ```text -Act as Dispatcher. Read the charter, Plan, State, Routing, and ratified adoption -record. Draft one bounded work order for the project's genuine next product -task. Generate and validate its boundaries, but do not activate it. Return the -exact candidate and your scope rationale for my approval. +Act as a fresh Owner-Agent / Project-Architect. 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. Draft, but do not activate or +implement, the smallest genuine work order or bounded external Operator packet. +Lead with a concise Recommendation and material tradeoff; keep the detailed +packet behind it as 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 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 review after implementation. The onboarding coordinator stops here and +does not continue into project work. ``` -After you approve and activate that work order, start a fresh Implementer: +The Architect 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 +a new user-owned task must be explicitly included in that request. Once you +approve it, the Architect performs every authorized mechanical step available +without asking the same question again. It still does not infer ratification, +activate a work order it was told only to draft, or implement product work. + +After you separately approve and activate a work order, start a fresh Implementer: ```text -Act as Implementer for the active work order only. Confirm the active dispatch +Act as a fresh Implementer for the active work order only. Confirm the active dispatch and required live-wall canary before mutation. Execute the order, preserve RED and GREEN evidence, write its report, and stop before acceptance or closeout. ``` diff --git a/checks/check_coordinator_release.py b/checks/check_coordinator_release.py index ff98b83..7078cf7 100644 --- a/checks/check_coordinator_release.py +++ b/checks/check_coordinator_release.py @@ -242,12 +242,56 @@ def check_candidate(candidate: Path, expected_tag: str) -> None: + ", ".join(residue) ) + adopted = workspace / "adopted-project" + governance = adopted / "governance" + decisions = governance / "decisions" + decisions.mkdir(parents=True) + for name in ("PLAN.md", "STATE.md", "ROUTING.md"): + (governance / name).write_text( + f"# {name}\n", encoding="utf-8", newline="\n" + ) + (decisions / "DR-001.md").write_text( + "# Adoption record\n", encoding="utf-8", newline="\n" + ) + adopted_before = tree_digest(adopted) + adopted_result = run( + [str(command), "start", "--project-root", str(adopted)], + cwd=workspace, + environment=environment, + label="installed adopted-lockout route", + ) + required_route_text = ( + "Observed lifecycle state: adopted_lockout", + "Act as a fresh Owner-Agent / Project-Architect", + "one combined disposition and action", + "Do not ask for the same decision again", + ) + route_output = " ".join(adopted_result.stdout.split()) + missing_route_text = [ + text for text in required_route_text + if text not in route_output + ] + if missing_route_text: + raise ReleaseCheckError( + "installed adopted-lockout route omitted: " + + ", ".join(missing_route_text) + ) + if (adopted / ".writwall-bootstrap").exists(): + raise ReleaseCheckError( + "installed adopted-lockout route published a bootstrap" + ) + if tree_digest(adopted) != adopted_before: + raise ReleaseCheckError( + "installed adopted-lockout route changed target bytes" + ) + verify_candidate_unchanged(candidate, before) print("OK: coordinator release candidate passed") print(f" installed version : {expected_version}") print(" installed command : help and real start passed under normal bytecode behavior") print(" complete handoff : all required packets present; no bytecode residue") + print(" adopted lockout : fresh Architect route; zero target-byte change") print(" candidate unchanged: complete-tree digest preserved") diff --git a/docs/day-zero-coordinator.md b/docs/day-zero-coordinator.md index 1f0b447..1898c21 100644 --- a/docs/day-zero-coordinator.md +++ b/docs/day-zero-coordinator.md @@ -1,8 +1,9 @@ # Day-zero coordinator -The day-zero coordinator is Writwall's single human entry point. It is a -standard-library Python command that runs from a clean Writwall source -distribution and prepares a temporary handoff inside the target project. +The day-zero coordinator is Writwall's single human entry point across the +project lifecycle. It is a standard-library Python command that runs from a clean Writwall +source distribution. It prepares a temporary handoff inside a clean/new target +or prints a zero-write fresh-role handoff for every later valid state. It is intentionally smaller than an AI agent. It does not interpret intent, install a capability wall, ratify adoption, activate a work order, contact a @@ -64,31 +65,28 @@ mail routing, while historical mailbox inventory/cleanup/migration remains a separate function. It supplies no provider, domain, account, record, or mailbox value. -## What it observes +## What it observes and routes -The coordinator reads the target repository before assigning a role: +The coordinator classifies before intake or privacy initialization: -- no Writwall markers: clean/new bootstrap; -- incomplete Writwall-shaped material: recovery coordinator; -- adopted repository with no pointer: lockout and Dispatcher route; -- a valid pointer to an exact `status: ACTIVE` order: Implementer route; -- closed work in history with no pointer: retired lockout, never an active- - Implementer continuation; -- malformed, missing, retired, or contradictory active state: stop without - generating a handoff. +| Human command | Observed state | Fresh role receiving output | Prior session stops | Target bytes | +|---|---|---|---|---| +| `writwall start --project-root ` | No Writwall markers | Adoption coordinator | Launcher returns; coordinator later stops at closeout | Create-only bootstrap may be added | +| Same command | Partial `.writwall-bootstrap/` or Writwall-shaped material | Recovery coordinator | Incomplete adoption or locked session | Unchanged | +| Same command | Adopted or retired lockout | Owner-Agent / Project-Architect | Onboarding coordinator or prior work session | Unchanged | +| Same command | Exact pointer to the only `status: ACTIVE` order | Bounded Implementer | Prior coordinator or Implementer context | Unchanged | +| Same command | Malformed, missing, retired, or contradictory active state | No role; precise diagnostic | Invoking session | Unchanged | Repository bytes are authoritative for this observation. A prior chat message or remembered work-order name is not lifecycle state. ## What it creates -The command refuses to run if `/.writwall-bootstrap/` already exists, -including a dangling symlink, junction, or reparse entry. It also rejects -linklike lifecycle paths and stops when more than one ACTIVE work order exists. -It builds a complete temporary sibling on the same filesystem and publishes it -with one atomic directory rename; a caught pre-publication failure verifies -that the stage is gone and leaves no target output. -On success that temporary, create-only directory contains: +Only clean/new mode performs intake, initializes privacy, or creates target +bytes. In that mode, the command builds a complete temporary sibling on the +same filesystem and publishes it with one atomic directory rename; a caught +pre-publication failure verifies that the stage is gone and leaves no target +output. On success that temporary, create-only directory contains: - `HANDOFF.md`: observed state, role routing, exact next prompt, authority boundaries, and optional Owner-time instructions; @@ -101,7 +99,11 @@ On success that temporary, create-only directory contains: - `operations/*.md`: inert packet scaffolds for separately named external Operator functions. -The bundle remains local until the authorized recorder no longer needs it and +An existing partial `.writwall-bootstrap/` is recovery evidence: it is never +overwritten or republished. Linklike lifecycle paths, multiple ACTIVE work +orders, and other contradictions fail closed before any write. + +The clean/new bundle remains local until the authorized recorder no longer needs it and is removed before the adoption commit. The command does not modify an existing project file, register a hook, create `.claude/active-wo.txt`, or claim the project has adopted Writwall. @@ -130,6 +132,14 @@ candidate. Count human reading, deciding, responding, authentication, and unavoidable UI work. Exclude agent execution and waiting. If capture is declined, the handoff records `NOT REPORTED`; no later agent reconstructs it. +For adopted/retired lockout, the printed Project-Architect prompt begins +read-only, derives lifecycle from bytes, keeps detailed packets behind a concise +recommendation and material tradeoff, and requests one combined disposition and +safe next action. If that action requires a new user-owned task, the request +explicitly includes creation and dispatch. Approval triggers every mechanically +available authorized step without a redundant permission turn; it never erases +Owner ratification or fresh review. + The manual routes remain in [`START-HERE.md`](../START-HERE.md) and [`ADOPTING.md`](../ADOPTING.md) for environments without a supported Python runtime or for recovery that requires an external coordinator. diff --git a/governance/LOG.md b/governance/LOG.md index 5d57809..b35608b 100644 --- a/governance/LOG.md +++ b/governance/LOG.md @@ -872,6 +872,71 @@ were denied by its ACL; it contains no accepted release bytes or private input. No public mutation, release, tag, deployment, visibility change, or External Pilot A mutation occurred before acceptance. +### Post-pilot WO-WW-015 completed record + +WO-WW-015 is post-pilot and does not add an eleventh metrics row or change the +accepted ten-order aggregate. The Owner accepted it on 2026-09-01 and reported +active minutes **NOT REPORTED**. + +The single `writwall start --project-root ` entry point now separates +clean/new intake from lifecycle routing. Recovery, adopted/retired lockout, and +active-work-order states skip intake and preserve target bytes while handing +off to the correct fresh role. Adoption closeout stops at an explicit fresh +Owner-Agent / Project-Architect boundary. Architect interaction now leads with +a concise recommendation, keeps the full packet as supporting evidence, and +combines disposition with the mechanically available next action so an +approved dispatch is not requested twice. + +Fresh review found a stale-classification race and three mixed-state fail-open +routes. Public-interface RED/GREEN corrections added pre-write lifecycle +revalidation and fail-closed recovery-marker contradictions. Windows passed +750 tests with two skips. Native Ubuntu passed the affected lifecycle tests and +the exact two installed-wheel tests under a temporary compatible declared +build backend. Two independent 136-file public candidates passed projection +and installed-release gates and were byte-identical at complete-tree ledger +SHA-256 `0CE941705CE8FEAB868220D198DDC37B12F5A213A3C482AF6E9ED43DDA19088D`. +Fresh final re-review returned **ACCEPT — HIGH confidence**. + +Accepted deviations are preserved in the report: the Codex implementation +surface was not physically governed by the Claude hook; early test bytecode +residue was caught and removed; the native environment first lacked pip and +then a compatible declared build backend; one temporary-backend setup command +was recovered with Ubuntu restored and all temporary paths removed; and one +read-denied history search printed filenames only, without retrieving content. +No public mutation, release, tag, deployment, pilot mutation, or successor +activation occurred before acceptance. Public issue #24 remains open pending a +separately authorized public projection and PR. + +### Post-pilot WO-WW-016 completed record + +WO-WW-016 is post-pilot and does not add an eleventh metrics row or change the +accepted ten-order aggregate. The Owner accepted it on 2026-09-02 and reported +active minutes **NOT REPORTED**. + +The immutable `v0.9.2` release predates WO-WW-015, so the accepted lifecycle +handoff could not truthfully remain packaged or documented as that release. +Package metadata, conditional tagged-install guidance, contribution and +publication commands, and executable synchronization tests now agree on +`0.9.3` / `v0.9.3`. Current onboarding text says the tag is not yet published; +historical `v0.9.0` through `v0.9.2` facts remain unchanged. + +Windows passed 750 tests with two skips. Native Ubuntu passed 17 release tests +and the installed `v0.9.3` gate using an Owner-authorized disposable compatible +backend. Two independent 136-file candidates passed Windows installed-release +and final projection gates and were byte-identical at complete-tree ledger +SHA-256 `D7AC9887601144906E416AEC78ACE71944DCC6B580588EC709FF4011B2CB3649`. +Fresh review returned **ACCEPT — HIGH confidence**. + +Accepted deviations are preserved in the report: the first sandboxed native +invocation was denied before execution; Ubuntu lacked its optional `venv` +component, so the temporary backend used an isolated module directory; and one +status-wrapper quoting error made an otherwise green test run diagnostic before +a corrected complete rerun passed. Temporary candidates and dependency paths +were deleted and proven absent. No out-of-grant mutation succeeded. No public +branch, PR, issue closure, merge, tag, or release occurred before acceptance. +Issue #24 proceeds only through the separately authorized post-closeout PR and +protected-CI merge; tagging and releasing `v0.9.3` remain unauthorized. + --- ## Column definitions diff --git a/governance/PLAN.md b/governance/PLAN.md index e902bdc..e77ad1e 100644 --- a/governance/PLAN.md +++ b/governance/PLAN.md @@ -630,3 +630,63 @@ the Windows and Ubuntu projection suites each passed 68 tests; two 136-file candidates passed projection and installed `v0.9.2` release gates and were byte-identical. Fresh corrected-record review returned ACCEPT/HIGH. Resume the authorized public release and External Pilot A tail. + +## 24. External Pilot A disposition and terminal lifecycle routing — 2026-09-01 + +Release `v0.9.2` is published. External Pilot A successfully adopted Writwall, +completed its channel-local Windows birth test, and closed two genuine work +orders with no successful forbidden mutation. That result remains a successful +adoption and operating result. Public issue #24 records one product lesson: +onboarding did not terminate at an explicit fresh Project-Architect boundary, +so the already-long adoption coordinator continued as Architect by +conversational momentum. + +**WO-WW-015 — COMPLETE, accepted 2026-09-01: terminal Architect handoff and +lifecycle-aware start routing.** `writwall start --project-root ` is +the sole human entry point. Clean/new projects retain interview and create-only +bootstrap; partial/recovery, adopted or retired lockout, and active-work-order +states skip intake and change no target bytes while routing to the correct +fresh role. Adoption closeout presents the exact fresh Owner-Agent / Project- +Architect handoff and stops before genuine project work. + +Fresh Architect evidence added one interaction correction: lead with a concise +recommendation and material tradeoff, keep the complete packet as supporting +evidence, and ask once for a combined disposition and mechanically available +next action. When creating and dispatching a new user-owned task is that action, +the same request names it explicitly; permission is not inferred later. Once +approved, the Architect performs every mechanically available authorized step +without asking the same decision twice. Human ratification, work-order +activation boundaries, and distinct fresh review remain unchanged. + +This correction authorizes no closeout, public mutation, release, pilot +mutation, external account access, or successor activation. + +Final Windows verification passed 750 tests with two skips. Native Ubuntu +passed the affected lifecycle tests and the exact two installed-wheel tests +under a temporary compatible declared build backend. Two independent 136-file +public candidates were checker-clean and byte-identical. Fresh final re-review +returned **ACCEPT — HIGH confidence**. Owner active minutes were **NOT +REPORTED**. No successor work order is active; public issue #24 remains open +pending a separately authorized projection and PR. + +## 25. v0.9.3 release identity — 2026-09-02 + +The first post-WO-WW-015 public candidates were independently checker-clean, +installed-release-clean, and byte-identical at 136 files. Fresh publication +review blocked them before any public branch or PR because immutable release +`v0.9.2` predates WO-WW-015 while current install guidance described the new +lifecycle behavior under that old tag. Both candidates were deleted. + +**WO-WW-016 — COMPLETE, accepted 2026-09-02: release identity 0.9.3.** Current +package metadata, conditional install guidance, and release-check commands now +agree on `0.9.3` / `v0.9.3` while explicitly stating that the tag is not yet +published. Truthful historical `v0.9.0` through `v0.9.2` evidence remains. + +Windows passed 750 tests with two skips; native Ubuntu passed 17 release tests +and the installed `v0.9.3` gate. Two independent 136-file candidates passed +their Windows installed-release and final projection gates and were byte- +identical. Fresh review returned **ACCEPT — HIGH confidence**. Owner active +minutes were **NOT REPORTED**. After ordinary closeout, repeat two projections +and fresh publication review, open the issue #24 PR, and merge only after +required CI passes. Tagging and releasing `v0.9.3` remain separate, +unauthorized actions. diff --git a/governance/STATE.md b/governance/STATE.md index 03aa924..ada4e07 100644 --- a/governance/STATE.md +++ b/governance/STATE.md @@ -19,46 +19,29 @@ accepted WO-WW-008 State derivation endpoint correction, accepted WO-WW-009 patch-release identity correction, and accepted WO-WW-010 bootstrap expected-denial probe contract correction, accepted WO-WW-011 v0.9.2 release identity correction, accepted WO-WW-012 installed bootstrap-bundle completeness -correction, accepted WO-WW-013 post-closeout projection-reference truth, and -accepted WO-WW-014 public current-record host-path privacy. -Release `v0.9.1` is public. External Pilot A reached an unratified bootstrap -handoff and exposed public issue #16: normal first use could copy Python -bytecode residue into coordinator output. Public PR #17 merged the accepted -correction and closed issue #16. **WO-WW-009 is COMPLETE and accepted**; package -metadata, current install guidance, and the mandatory intended-tag gate agree on -`v0.9.1`. The authorized post-closeout projection, public PR, protected-CI -merge, patch release, and pilot regeneration completed. External Pilot A then -exposed public issue #22 when a conforming provider followed the bootstrap -charter and refused the required no-pointer birth-test probes before hook -dispatch. **WO-WW-010 is COMPLETE and accepted**; its authorized private -closeout, public patch-release tail, and one regenerated External Pilot A birth -test are in progress. The first two post-closeout candidates were checker-clean -and byte-identical but correctly failed the intended-tag gate because their -current release identity remained `v0.9.1`; they were deleted. **WO-WW-011 is -COMPLETE and accepted** with exact current identity `v0.9.2`; its authorized -private closeout, public release tail, and one External Pilot A birth test are in -progress. The first final publication review blocked because the installed wheel -and release gate both omitted the new bootstrap addendum; both candidates were -deleted. **WO-WW-012 is COMPLETE and accepted**: the canonical addendum is now -packaged in the installed adoption bundle and required by the external release -gate. Its authorized private closeout, public release tail, and one External -Pilot A birth test are in progress. The first post-closeout candidates failed -closed on one unqualified private-only path in the new LOG record and were -deleted. **WO-WW-013 is COMPLETE and accepted**: the record is qualified, the -issued Plan no longer repeats the literal, and the final pair passed. The public -release and pilot tail remain in progress. No external media content, website, -DNS, or mail mutation is active. Fresh final publication review then found one -concrete sibling-project host path in an earlier current-use Plan section. -**WO-WW-014 is COMPLETE and accepted**: the Plan reference is host-neutral and -the projection checker now rejects concrete Windows, macOS, Linux-home, and -mounted-drive paths in current public records without echoing their values. The -authorized public release and pilot tail resume. - -**Derived:** 2026-08-31 from the ten accepted pilot records, the Doctrine 9.3.1 +correction, accepted WO-WW-013 post-closeout projection-reference truth, +accepted WO-WW-014 public current-record host-path privacy, accepted WO-WW-015 +terminal Architect handoff and lifecycle-aware start routing, and accepted +WO-WW-016 release identity 0.9.3. +Release `v0.9.2` is public. External Pilot A successfully adopted Writwall, +completed its channel-local Windows birth test, and closed two genuine work +orders with no successful forbidden mutation. Public issue #24 records the +product lesson that onboarding did not terminate in an explicit fresh Project- +Architect handoff, allowing the already-long adoption coordinator to continue +as Architect by conversational momentum. Fresh Architect evidence also showed +a long packet followed by a redundant second dispatch-permission question. +WO-WW-015 corrected those product interactions without reclassifying the pilot +as a failure: clean/new retains intake and create-only bootstrap; later valid +states route without target-byte changes; adoption closeout stops at the fresh +Architect boundary; and Architect requests use progressive disclosure plus one +combined disposition/action gate. No external media content, website, DNS, or +mail mutation is active. + +**Derived:** 2026-09-01 from the ten accepted pilot records, the Doctrine 9.3.1 fresh-agent evaluation, ratified DR-002 and project-migration DR-003, and accepted WO-PL-017 through WO-PL-023 and WO-PL-025 through WO-PL-033 records, -the WO-PL-024 sequencing recovery, the verified public-release event, and the -accepted WO-WW-001 through WO-WW-014 closeout records. +the WO-PL-024 sequencing recovery, the verified public-release events, the +accepted WO-WW-001 through WO-WW-016 closeout records. **Boundary:** post-adoption, all 10 counted pilot work orders and their evaluation complete; WO-PL-017 remediation complete; DR-003 ratified; WO-PL-018 through WO-PL-023 complete; WO-PL-024 void before implementation; @@ -70,12 +53,12 @@ the current identity with the two-line wall glyph; WO-PL-039 complete and accepted, with public PR #5 merged after the required CI passed and issue #4 closed; WO-PL-040 complete and accepted; public PR #8 merged and issue #1 closed; the historical `WO-PL` series ends at 040; **WO-WW-001 through -WO-WW-014 are COMPLETE and accepted**; public PR #9, #12, #13, and #17 merged with -protected CI green; public issue #11 is closed; public issue #14 records the -coordinator release gate; release `v0.9.0` is published; public issue #16 -records the corrected cache-residue defect found by the first unratified -external media bootstrap. No external media content, website, DNS, or mail -mutation is active. +WO-WW-016 are COMPLETE and accepted**; no work order is active; public PR #9, +#12, #13, and #17 merged with protected CI green; public issue #14 records the +coordinator release gate; release `v0.9.2` is published; External Pilot A +adopted successfully and closed two genuine work orders; public issue #24 +records the terminal-Architect handoff defect. No external media content, +website, DNS, or mail mutation is active. The hash of the commit containing this file is intentionally recorded only externally. @@ -144,7 +127,9 @@ externally. | WO-WW-012 | **COMPLETE**, accepted 2026-09-01; the installed wheel now includes the exact canonical bootstrap addendum and the release gate requires it as part of every complete handoff. Removing the packaging entry fails deterministically. Windows passed 738 tests with two skips; native Ubuntu passed 16 release tests; two 136-file candidates were projection/release-clean and byte-identical; corrected fresh review returned **ACCEPT — HIGH confidence**. Owner active minutes **NOT REPORTED**. The authorized v0.9.2 release tail and one External Pilot A birth test remain in progress | | WO-WW-013 | **COMPLETE**, accepted 2026-09-01; the completed WO-WW-012 LOG record now qualifies its omitted private-only path on the same physical line. The unchanged checker also caught an unnecessary repetition in the issued Plan before publication. Two final 136-file candidates passed projection and installed `v0.9.2` release gates and were byte-identical; fresh review returned **ACCEPT — HIGH confidence**. Owner active minutes **NOT REPORTED**. Product and checker bytes were unchanged; the public release and pilot tail remain in progress | | WO-WW-014 | **COMPLETE**, accepted 2026-09-01; the earlier current-use Plan reference is host-neutral and the projection checker rejects concrete Windows, macOS, Linux-home, and mounted-drive paths in current public records without echoing values. Windows passed 743 tests with two skips; Windows and Ubuntu projection suites each passed 68 tests; two 136-file candidates were projection/release-clean and byte-identical; corrected fresh review returned **ACCEPT — HIGH confidence**. Owner active minutes **NOT REPORTED**. The authorized public release and External Pilot A tail resume | -| Post-pilot sequence | **WO-PL-017 through WO-PL-023 COMPLETE**; WO-PL-024 **VOID BEFORE IMPLEMENTATION**; WO-PL-025 through WO-PL-040 **COMPLETE**; historical `WO-PL` identifiers end at 040; **WO-WW-001 through WO-WW-014 COMPLETE and accepted**; public PR #9, #12, #13, #17, and #21 merged with protected CI green; public issues #1, #4, #10, #11, #16, and #20 closed; public issue #14 records the accepted release gate; `v0.9.1` is published; issue #22 proceeds through the authorized v0.9.2 release tail; external media regeneration, website, DNS, and mail pilots remain queued | +| WO-WW-015 | **COMPLETE**, accepted 2026-09-01; lifecycle-aware `writwall start` now preserves clean/new intake while routing recovery, adopted/retired lockout, and active-work-order states to the correct fresh role with zero target-byte change. Adoption closes at an explicit fresh Project-Architect handoff using progressive disclosure and one combined approval/action gate. Windows passed 750 tests with two skips; native Ubuntu affected and installed-wheel gates passed; two 136-file candidates were checker-clean and byte-identical; fresh final re-review returned **ACCEPT — HIGH confidence**. Owner active minutes **NOT REPORTED**. Records retained in `governance/history/`; public issue #24 remains open pending separately authorized public projection/PR work | +| WO-WW-016 | **COMPLETE**, accepted 2026-09-02; current package metadata, conditional install guidance, and executable release checks agree on `0.9.3` / `v0.9.3` without claiming the tag is published. Historical release facts remain unchanged. Windows passed 750 tests with two skips; native Ubuntu passed 17 release tests and the installed gate; two 136-file candidates were projection/release-clean and byte-identical; fresh review returned **ACCEPT — HIGH confidence**. Owner active minutes **NOT REPORTED**. Records retained in `governance/history/`; issue #24 proceeds through the authorized post-closeout public PR while tag and release remain unauthorized | +| Post-pilot sequence | **WO-PL-017 through WO-PL-023 COMPLETE**; WO-PL-024 **VOID BEFORE IMPLEMENTATION**; WO-PL-025 through WO-PL-040 **COMPLETE**; historical `WO-PL` identifiers end at 040; **WO-WW-001 through WO-WW-016 COMPLETE and accepted**; no work order is active; public PR #9, #12, #13, #17, and #21 merged with protected CI green; public issues #1, #4, #10, #11, #16, and #20 closed; public issue #14 records the accepted release gate; `v0.9.2` is published; `v0.9.3` is not yet tagged or released; External Pilot A adopted and closed two genuine work orders; issue #24 remains open pending the authorized public PR; website, DNS, and mail pilots remain queued | | Bootstrap history | Eleven completed work orders retained as uncounted pre-adoption evidence under `archive/pre-adoption-bootstrap/` | ### Verification accepted at WO-PL-016 closeout diff --git a/identity/legacy-references.json b/identity/legacy-references.json index 97ba437..4acba2b 100644 --- a/identity/legacy-references.json +++ b/identity/legacy-references.json @@ -16,7 +16,7 @@ { "path": "README.md", "context": "migration_provenance", - "sha256": "e81903350f305351ccd52e7acaa584b4683b7ba65fa1ecbeb2c048eec357e483" + "sha256": "2bef9b65ca9ff3aec8dc8149f185f337c7f0e4d71c79c62661f37f9c1a543c82" }, { "path": "SELF-HOSTING.md", @@ -94,19 +94,19 @@ { "path": "governance/LOG.md", "context": "historical_pilot_summary", - "sha256": "6cd8bb10ebd50071b2f355c1934fdbb525452dabeee7cc98f24eaddf03a1fef6", + "sha256": "8f1ae9b4f005a31eed74e9f68429fed33fc6ffed2bd31e05de59327d4f9db220", "projection_transform": "private_evidence_redaction" }, { "path": "governance/PLAN.md", "context": "ratified_historical_intent", - "sha256": "fcbdd620daea8b69e35ecb859f08f5332ceb89f6ac5dc9a293096b4190e4d437" + "sha256": "3b5645777ed304ce7205fbd797323320fd10ddd421bf4b9562c5bd3c28b4df9f" }, { "path": "governance/STATE.md", "context": "mixed_current_state_and_history", - "sha256": "0340951b66d939bcdff576a8f0bff66401aea4353f021882a81646370994c474", - "projection_sha256": "609d2afa9e0bb198caebbfc08aee0b541fd713041b106d66100b5670bdf9639a" + "sha256": "4939f44b509ce43e356f55d40f7ec506e29f3fb0a236096b8353729f59f68e3c", + "projection_sha256": "abd6676fd83bb1e7a480048ffc3e717fe9184a41e5c887ef223c40d3bed113a1" }, { "path": "governance/decisions/DR-001.md", @@ -127,7 +127,7 @@ { "path": "tests/test_distribution.py", "context": "historical_evidence_fixture", - "sha256": "22901437098bb4883153af4f8d02f14e02585343791ca813aa425e2956510927" + "sha256": "e5e6df9ead5effa20ee48167f070aa43eb8716e4df66fd997df65d6c2edc7c69" }, { "path": "tests/test_identity_migration.py", diff --git a/pyproject.toml b/pyproject.toml index b176d1b..955eb6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "writwall" -version = "0.9.2" +version = "0.9.3" description = "Start governed project work from an idea." requires-python = ">=3.10" license = "Apache-2.0" diff --git a/scripts/start_writwall.py b/scripts/start_writwall.py index cf8b2b2..03e42ee 100644 --- a/scripts/start_writwall.py +++ b/scripts/start_writwall.py @@ -30,6 +30,21 @@ "record values, or other secrets. This tool writes your answers as plain " "text inside the target project." ) +PROJECT_ARCHITECT_PROMPT = """Act as a fresh Owner-Agent / Project-Architect. 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. Draft, but do not activate or +implement, the smallest genuine work order or bounded external Operator packet. +Lead with a concise Recommendation and material tradeoff; keep the detailed +packet behind it as 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 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 review after implementation. The onboarding coordinator stops here and +does not continue into project work.""" DNS_MAIL_SCENARIO = ( "DNS provider selection", @@ -167,6 +182,18 @@ def _safe_pointer_target(project: Path, value: str) -> Path: def classify_project(project: Path) -> ObservedState: """Classify lifecycle state from repository bytes, never chat context.""" + bootstrap = project / OUTPUT_NAME + bootstrap_exists = _entry_exists(bootstrap) + if bootstrap_exists: + _safe_project_path(project, bootstrap, "bootstrap recovery marker") + + def reject_bootstrap_conflict(lifecycle: str) -> None: + if bootstrap_exists: + raise CoordinatorError( + "inconsistent state: .writwall-bootstrap recovery marker " + f"coexists with {lifecycle} lifecycle state" + ) + claude_dir = project / ".claude" if _entry_exists(claude_dir): resolved_claude = _safe_project_path(project, claude_dir, ".claude directory") @@ -227,6 +254,7 @@ def classify_project(project: Path) -> ObservedState: f"work order; observed ACTIVE records: {names}" ) relative = pointed_target.relative_to(project).as_posix() + reject_bootstrap_conflict("active work-order") return ObservedState( "active_work_order", ("activation pointer exists", f"pointed work order is ACTIVE: {relative}"), @@ -276,6 +304,7 @@ def classify_project(project: Path) -> ObservedState: ) if all(path.is_file() for path in core) and closed_records: + reject_bootstrap_conflict("retired lockout") return ObservedState( "retired_lockout", ( @@ -287,6 +316,7 @@ def classify_project(project: Path) -> ObservedState: if all(path.is_file() for path in core) and any( path.is_file() for path in adoption_records ): + reject_bootstrap_conflict("adopted lockout") return ObservedState( "adopted_lockout", ( @@ -296,6 +326,7 @@ def classify_project(project: Path) -> ObservedState: ) writwall_markers = ( + project / OUTPUT_NAME, project / ".claude" / "hooks" / "wo_capability_wall.py", project / ".claude" / "settings.json", governance / "PLAN.md", @@ -446,7 +477,7 @@ def next_prompt(state: ObservedState) -> tuple[str, str]: if state.name == "clean_new": return ( "Adoption coordinator before wall registration", - """Act as my Writwall adoption coordinator, not as an Implementer. Read + f"""Act as my Writwall adoption coordinator, not as an Implementer. Read `.writwall-bootstrap/writwall-adopt/SKILL.md` and use bootstrap mode. Treat `.writwall-bootstrap/intake.json` as unratified intake, not authority. I decide and ratify; perform every clerical step an authorized recorder may perform. @@ -458,30 +489,33 @@ def next_prompt(state: ObservedState) -> tuple[str, str]: remains forbidden; the addendum permits only exact expected-denial probes named by a durably Owner-ratified lifecycle and confers no mutation authority. Denial is the only valid outcome, and any success stops adoption. Remove it before the -adoption commit. Do not begin product work or WO-001 before adoption.""", +adoption commit. Do not begin product work or WO-001 before adoption. + +After adoption closeout, present the following exact fresh-role handoff and +stop. The onboarding coordinator stops before product work: + +{PROJECT_ARCHITECT_PROMPT}""", ) if state.name == "partial_bootstrap": return ( - "External recovery coordinator", - """Act as my Writwall accidental-overlay or incomplete-adoption recovery coordinator, -not as an Implementer. Read `.writwall-bootstrap/writwall-adopt/SKILL.md`. -Inventory only; do not delete, overwrite, move, install, register, activate, or -invent missing intent. Use the observed-state evidence in the handoff, propose -an exact disposition packet, and ask one question at a time.""", + "Fresh external recovery coordinator", + """Act as a fresh recovery coordinator for this accidental overlay or incomplete +Writwall adoption, not as an Implementer. Begin read-only. Use a complete local +Writwall source or adoption bundle outside the locked session; do not assume a +partial project-local bundle is complete. Inventory only: do not delete, +overwrite, move, install, register, activate, or invent missing intent. Verify +the lifecycle from repository bytes, propose an exact disposition packet, and +ask one question at a time. The prior session stops here.""", ) if state.name in {"adopted_lockout", "retired_lockout"}: return ( - "Dispatcher for a new candidate work order", - """Act as Dispatcher. The repository is in observed lockout; no active work order -is established. Read the charter, Plan, State, Routing, and ratified adoption -record. Draft one bounded work order for the project's genuine next task. -Generate and validate its boundaries, but do not activate it. Return the exact -candidate and scope rationale for Owner approval.""", + "Fresh Owner-Agent / Project-Architect", + PROJECT_ARCHITECT_PROMPT, ) if state.name == "active_work_order": return ( - "Walled repository Implementer", - """Act as Implementer for the active work order only. Re-read the activation + "Fresh walled repository Implementer", + """Act as a fresh Implementer for the active work order only. Re-read the activation pointer and pointed work order from repository bytes, confirm the active dispatch and required live-wall canary before mutation, execute only its grant, write its report, and stop before acceptance or closeout.""", @@ -489,6 +523,19 @@ def next_prompt(state: ObservedState) -> tuple[str, str]: raise CoordinatorError(f"inconsistent state: unsupported classification {state.name!r}") +def emit_lifecycle_handoff(state: ObservedState) -> None: + """Print the next fresh-role handoff without changing target bytes.""" + role, prompt = next_prompt(state) + print(f"Observed lifecycle state: {state.name}") + for item in state.evidence: + print(f" - {item}") + if state.active_work_order: + print(f"Pointed work order: {state.active_work_order}") + print(f"Next role: {role}") + print("\nCopy this prompt into a fresh session:\n") + print(prompt) + + def render_time(owner_time: str) -> str: if owner_time == "yes": return """**Owner active-minute capture: ENABLED.** Start when the first intake @@ -991,6 +1038,21 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: return parser.parse_args(argv) +def resolve_project_root(value: str | None) -> Path: + if not value: + raise CoordinatorError("missing required intake: project root") + supplied_project = Path(value).expanduser() + if supplied_project.is_symlink(): + raise CoordinatorError("target project must not be a symlink") + try: + project = supplied_project.resolve(strict=True) + except OSError as exc: + raise CoordinatorError(f"target project directory is not readable: {exc}") from exc + if not project.is_dir() or project.is_symlink(): + raise CoordinatorError("target project must be an existing, non-symlink directory") + return project + + def normalize_args(args: argparse.Namespace) -> tuple[argparse.Namespace, Path, tuple[str, ...]]: if not args.non_interactive: args = interactive_args(args) @@ -1058,15 +1120,7 @@ def complete_answer(value: object) -> bool: "be both a constraint and a non-goal" ) - supplied_project = Path(args.project_root).expanduser() - if supplied_project.is_symlink(): - raise CoordinatorError("target project must not be a symlink") - try: - project = supplied_project.resolve(strict=True) - except OSError as exc: - raise CoordinatorError(f"target project directory is not readable: {exc}") from exc - if not project.is_dir() or project.is_symlink(): - raise CoordinatorError("target project must be an existing, non-symlink directory") + project = resolve_project_root(args.project_root) if args.brief_file: brief = Path(args.brief_file).expanduser() @@ -1093,12 +1147,23 @@ def main(argv: list[str] | None = None) -> int: PrivacyScreenError, add_identifier, initialize, ) - args, project, functions = normalize_args(parse_args(argv)) + args = parse_args(argv) + project = resolve_project_root(args.project_root) + state = classify_project(project) + if state.name != "clean_new": + emit_lifecycle_handoff(state) + return 0 + args, project, functions = normalize_args(args) + current_state = classify_project(project) + if current_state.name != state.name: + raise CoordinatorError( + "lifecycle changed during intake: expected clean_new, observed " + f"{current_state.name}; no privacy or bootstrap bytes were created" + ) if _entry_exists(project / OUTPUT_NAME): raise CoordinatorError( f"create-only stop: {OUTPUT_NAME} already exists; nothing was overwritten" ) - state = classify_project(project) try: privacy_count = initialize(project) for identifier in args.private_identifier: diff --git a/skills/writwall-adopt/SKILL.md b/skills/writwall-adopt/SKILL.md index 850bc67..b05c6ba 100644 --- a/skills/writwall-adopt/SKILL.md +++ b/skills/writwall-adopt/SKILL.md @@ -244,6 +244,31 @@ Report to the Owner: each decision recorded and whose words it came from; every The Owner's ratification of the decision packet **was** the adoption decision. The commit and the mechanics above only record it. So when they have all succeeded, report completion plainly and stop — do not ask the Owner to confirm again what they have already ratified, and do not describe adoption as pending their further word. If any step did not succeed, say exactly which one and stop there instead; a closeout you cannot complete is reported, never assumed. +After a successful closeout report, present the exact handoff below for a fresh +Owner-Agent / Project-Architect, then stop. Do not continue as Architect, create +or dispatch a user-owned task, activate a work order, or begin product work in +the onboarding context. The fresh Architect may request task creation and +dispatch only by including them explicitly in its single combined approval +request. + +```text +Act as a fresh Owner-Agent / Project-Architect. 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. Draft, but do not activate or +implement, the smallest genuine work order or bounded external Operator packet. +Lead with a concise Recommendation and material tradeoff; keep the detailed +packet behind it as 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 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 review after implementation. The onboarding coordinator stops here and +does not continue into project work. +``` + ## 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 bc290c6..fc4d4ef 100644 --- a/tests/test_coordinator_release.py +++ b/tests/test_coordinator_release.py @@ -53,7 +53,7 @@ def setUp(self) -> None: def run_checker(self, candidate: Path, *extra: str): arguments = [str(candidate), *extra] if "--expected-tag" not in extra: - arguments.extend(("--expected-tag", "v0.9.2")) + arguments.extend(("--expected-tag", "v0.9.3")) return subprocess.run( [sys.executable, "-B", str(CHECKER), *arguments], cwd=REPO_ROOT, @@ -104,6 +104,8 @@ def test_complete_external_candidate_installs_and_emits_full_handoff(self): self.assertIn("OK: coordinator release candidate passed", result.stdout) self.assertIn("installed command", result.stdout) self.assertIn("complete handoff", result.stdout) + self.assertIn("adopted lockout", result.stdout) + self.assertIn("zero target-byte change", result.stdout) self.assertIn("candidate unchanged", result.stdout) self.assertEqual(tree_digest(candidate), before) @@ -121,6 +123,26 @@ def test_broken_build_backend_fails_with_build_diagnostic(self): self.assertNotEqual(result.returncode, 0) self.assertIn("wheel build failed", result.stdout + result.stderr) + def test_installed_adopted_lockout_target_write_fails_release_gate(self): + candidate = self.make_candidate() + start = candidate / "scripts" / "start_writwall.py" + start.write_text( + start.read_text(encoding="utf-8").replace( + " emit_lifecycle_handoff(state)\n return 0\n", + " emit_lifecycle_handoff(state)\n" + " (project / 'forbidden-route-write.txt').write_text('changed')\n" + " return 0\n", + ), + encoding="utf-8", + newline="\n", + ) + result = self.run_checker(candidate) + self.assertNotEqual(result.returncode, 0) + self.assertIn( + "installed adopted-lockout route changed target bytes", + result.stdout + result.stderr, + ) + def test_installed_help_mismatch_fails_with_diagnostic(self): candidate = self.make_candidate() entry = candidate / "writwall_cli" / "__main__.py" @@ -227,15 +249,15 @@ def test_intended_release_tag_must_match_candidate_metadata(self): pyproject = candidate / "pyproject.toml" pyproject.write_text( pyproject.read_text(encoding="utf-8").replace( - 'version = "0.9.2"', 'version = "0.9.0"' + 'version = "0.9.3"', 'version = "0.9.0"' ), encoding="utf-8", newline="\n", ) - result = self.run_checker(candidate, "--expected-tag", "v0.9.2") + result = self.run_checker(candidate, "--expected-tag", "v0.9.3") self.assertNotEqual(result.returncode, 0) self.assertIn( - "candidate version '0.9.0' does not match intended tag 'v0.9.2'", + "candidate version '0.9.0' does not match intended tag 'v0.9.3'", result.stdout + result.stderr, ) @@ -269,18 +291,21 @@ def test_candidate_mutation_is_rejected(self): def test_release_identity_and_public_payload_are_coherent(self): with (REPO_ROOT / "pyproject.toml").open("rb") as handle: project = tomllib.load(handle)["project"] - self.assertEqual(project["version"], "0.9.2") + self.assertEqual(project["version"], "0.9.3") readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") adopting = (REPO_ROOT / "ADOPTING.md").read_text(encoding="utf-8") contributing = (REPO_ROOT / "CONTRIBUTING.md").read_text(encoding="utf-8") publication = (REPO_ROOT / "PUBLICATION.md").read_text(encoding="utf-8") start = (REPO_ROOT / "START-HERE.md").read_text(encoding="utf-8") - tagged_archive = "archive/refs/tags/v0.9.2.zip" + tagged_archive = "archive/refs/tags/v0.9.3.zip" self.assertIn(tagged_archive, readme) self.assertIn(tagged_archive, adopting) self.assertIn(tagged_archive, start) - self.assertIn("--expected-tag v0.9.2", publication) - self.assertIn("--expected-tag v0.9.2", contributing) + self.assertIn("--expected-tag v0.9.3", publication) + self.assertIn("--expected-tag v0.9.3", contributing) + for document in (readme, adopting, start): + self.assertIn("not yet published", document) + self.assertIn("After", document) self.assertIn("Release `v0.9.0` first introduced", start) self.assertIn("Release `v0.9.1` corrected", start) self.assertIn("Release `v0.9.2` corrects", start) diff --git a/tests/test_distribution.py b/tests/test_distribution.py index 01e610d..41ffe22 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -20,6 +20,8 @@ import zipfile from pathlib import Path +from scripts.start_writwall import PROJECT_ARCHITECT_PROMPT + REPO_ROOT = Path(__file__).resolve().parents[1] SKIP_DIRS = {".git", "dist", "__pycache__", ".pytest_cache", "bootstrap"} IS_PROJECTION = all( @@ -353,6 +355,33 @@ def test_current_docs_record_the_operational_0_8_project_binding(self): self.assertNotIn("remains bound to 0.6", text) self.assertIn("operatively bound to Doctrine 0.8", text) + def test_terminal_architect_handoff_is_synchronized(self): + for relative in ( + "START-HERE.md", + "ADOPTING.md", + "skills/writwall-adopt/SKILL.md", + ): + with self.subTest(path=relative): + text = (REPO_ROOT / relative).read_text(encoding="utf-8") + self.assertIn(PROJECT_ARCHITECT_PROMPT, text) + + def test_human_ramp_and_coordinator_reference_carry_lifecycle_table(self): + for relative in ( + "README.md", + "START-HERE.md", + "ADOPTING.md", + "docs/day-zero-coordinator.md", + ): + with self.subTest(path=relative): + text = " ".join( + (REPO_ROOT / relative).read_text(encoding="utf-8").split() + ).lower() + for truth in ( + "partial", "adopted", "active work order", "target bytes", + "project-architect", "recovery coordinator", "implementer", + ): + self.assertIn(truth, text) + class CheckerFailureCategories(DistributionTestCase): def test_unclassified_former_identity_fails_release_gate(self): @@ -3192,7 +3221,12 @@ def test_readme_losing_single_entry_command_fails(self): self.assert_fails(self.check(), "onboarding") def test_start_here_losing_temporary_bundle_fails(self): - self.edit("START-HERE.md", ".writwall-bootstrap/", ".temporary-bootstrap/") + self.edit( + "START-HERE.md", + ".writwall-bootstrap/", + ".temporary-bootstrap/", + count=-1, + ) self.assert_fails(self.check(), "onboarding") def test_skill_losing_repository_state_rule_fails(self): diff --git a/tests/test_start_writwall.py b/tests/test_start_writwall.py index 7de180f..be1c926 100644 --- a/tests/test_start_writwall.py +++ b/tests/test_start_writwall.py @@ -40,7 +40,9 @@ def run_start(self, *extra: str, project: Path | None = None): [ sys.executable, "-B", - str(STARTER), + "-m", + "writwall_cli", + "start", "--non-interactive", "--project-root", str(project or self.project), @@ -97,6 +99,28 @@ def run_idea_start(self, *extra: str, project: Path | None = None): timeout=60, ) + def run_lifecycle_start(self, project: Path | None = None): + return subprocess.run( + [ + sys.executable, "-B", "-m", "writwall_cli", "start", + "--project-root", str(project or self.project), + ], + cwd=REPO_ROOT, + env=self.environment(), + capture_output=True, + text=True, + timeout=60, + ) + + @staticmethod + def tree_snapshot(root: Path) -> dict[str, bytes | None]: + return { + path.relative_to(root).as_posix(): ( + path.read_bytes() if path.is_file() else None + ) + for path in sorted(root.rglob("*")) + } + @property def output(self) -> Path: return self.project / ".writwall-bootstrap" @@ -328,14 +352,62 @@ def test_clean_project_creates_bundle_and_exact_handoff(self): self.assertIn("does not install or adopt Writwall", flat) self.assertIn("Do not enter passwords, API tokens", handoff) - def test_create_only_refuses_existing_output_without_overwrite(self): + def test_existing_bootstrap_routes_to_recovery_without_overwrite(self): self.output.mkdir() sentinel = self.output / "keep.txt" sentinel.write_text("unchanged", encoding="utf-8") - result = self.run_start() - self.assertNotEqual(result.returncode, 0) + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(self.tree_snapshot(self.project), before) self.assertEqual(sentinel.read_text(encoding="utf-8"), "unchanged") self.assertEqual(sorted(p.name for p in self.output.iterdir()), ["keep.txt"]) + self.assertIn("Observed lifecycle state: partial_bootstrap", result.stdout) + self.assertIn("Act as a fresh recovery coordinator", result.stdout) + + def test_lifecycle_change_during_interactive_intake_stops_before_any_write(self): + process = subprocess.Popen( + [ + sys.executable, "-B", "-m", "writwall_cli", "start", + "--project-root", str(self.project), + "--project-name", "Example project", + "--purpose", "Build a small, governed project.", + "--agent", "Codex", + "--location", "local workstation", + "--environment", "local repository only", + ], + cwd=REPO_ROOT, + env=self.environment(), + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + assert process.stdout is not None + prompt = "" + while "Track Owner active minutes?" not in prompt: + char = process.stdout.read(1) + if not char: + break + prompt += char + self.assertIn("Track Owner active minutes?", prompt) + + governance = self.project / "governance" + decisions = governance / "decisions" + decisions.mkdir(parents=True) + for name in ("PLAN.md", "STATE.md", "ROUTING.md"): + (governance / name).write_text(f"# {name}\n", encoding="utf-8") + (decisions / "DR-001.md").write_text( + "# Adoption record\n", encoding="utf-8" + ) + changed_state = self.tree_snapshot(self.project) + + stdout_tail, stderr = process.communicate("no\nyes\n\n\n", timeout=60) + self.assertNotEqual(process.returncode, 0, prompt + stdout_tail + stderr) + self.assertEqual(self.tree_snapshot(self.project), changed_state) + self.assertFalse(self.output.exists()) + self.assertFalse(self.state.exists()) + self.assertIn("lifecycle changed during intake", stderr) def test_missing_secret_confirmation_fails_before_output(self): result = subprocess.run( @@ -421,10 +493,14 @@ def test_active_pointer_routes_to_implementer_only_when_target_is_active(self): pointer = self.project / ".claude" / "active-wo.txt" pointer.parent.mkdir(parents=True) pointer.write_text("governance/work-orders/WO-001.md\n", encoding="utf-8") - result = self.run_start() + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertEqual(self.intake()["observed_state"], "active_work_order") - self.assertIn("Act as Implementer for the active work order only", self.handoff()) + self.assertEqual(self.tree_snapshot(self.project), before) + self.assertFalse(self.output.exists()) + self.assertFalse(self.state.exists()) + self.assertIn("Observed lifecycle state: active_work_order", result.stdout) + self.assertIn("Act as a fresh Implementer for the active work order only", result.stdout) def test_pointer_plus_second_active_order_stops_as_inconsistent(self): orders = self.project / "governance" / "work-orders" @@ -437,10 +513,15 @@ def test_pointer_plus_second_active_order_stops_as_inconsistent(self): pointer = self.project / ".claude" / "active-wo.txt" pointer.parent.mkdir(parents=True) pointer.write_text("governance/work-orders/WO-001.md\n", encoding="utf-8") - result = self.run_start() + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() self.assertNotEqual(result.returncode, 0) + self.assertEqual(self.tree_snapshot(self.project), before) self.assertFalse(self.output.exists()) - self.assertIn("only active", (result.stdout + result.stderr).lower()) + self.assertIn( + "activation pointer does not identify the only ACTIVE work order", + result.stderr, + ) def test_missing_pointer_with_closed_history_never_emits_resume_prompt(self): closed = self.project / "governance" / "history" / "WO-001.md" @@ -448,13 +529,16 @@ def test_missing_pointer_with_closed_history_never_emits_resume_prompt(self): closed.write_text("---\nid: WO-001\nstatus: CLOSED\n---\n", encoding="utf-8") for name in ("PLAN.md", "STATE.md", "ROUTING.md"): (self.project / "governance" / name).write_text(f"# {name}\n", encoding="utf-8") - result = self.run_start() + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertEqual(self.intake()["observed_state"], "retired_lockout") - handoff = self.handoff() - self.assertIn("Act as Dispatcher", handoff) - self.assertNotIn("resume", handoff.lower()) - self.assertNotIn("Act as Implementer", handoff) + self.assertEqual(self.tree_snapshot(self.project), before) + self.assertFalse(self.output.exists()) + self.assertFalse(self.state.exists()) + self.assertIn("Observed lifecycle state: retired_lockout", result.stdout) + self.assertIn("Act as a fresh Owner-Agent / Project-Architect", result.stdout) + self.assertNotIn("resume", result.stdout.lower()) + self.assertNotIn("Act as a fresh Implementer", result.stdout) def test_pointer_to_closed_order_is_inconsistent_and_creates_nothing(self): work_order = self.project / "governance" / "work-orders" / "WO-001.md" @@ -463,21 +547,93 @@ def test_pointer_to_closed_order_is_inconsistent_and_creates_nothing(self): pointer = self.project / ".claude" / "active-wo.txt" pointer.parent.mkdir(parents=True) pointer.write_text("governance/work-orders/WO-001.md\n", encoding="utf-8") - result = self.run_start() + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() self.assertNotEqual(result.returncode, 0) + self.assertEqual(self.tree_snapshot(self.project), before) self.assertFalse(self.output.exists()) - self.assertIn("inconsistent", (result.stdout + result.stderr).lower()) + self.assertIn( + "activation pointer resolves, but the work order status is 'CLOSED', not 'ACTIVE'", + result.stderr, + ) def test_partial_bootstrap_routes_to_recovery_coordinator(self): settings = self.project / ".claude" / "settings.json" settings.parent.mkdir(parents=True) settings.write_text("{}\n", encoding="utf-8") - result = self.run_start() + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(self.tree_snapshot(self.project), before) + self.assertFalse(self.output.exists()) + self.assertFalse(self.state.exists()) + self.assertIn("Observed lifecycle state: partial_bootstrap", result.stdout) + self.assertIn("Act as a fresh recovery coordinator", result.stdout) + + def test_partial_bootstrap_directory_routes_without_republication(self): + self.output.mkdir() + sentinel = self.output / "HANDOFF.md" + sentinel.write_text("incomplete bootstrap\n", encoding="utf-8") + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertEqual(self.intake()["observed_state"], "partial_bootstrap") - self.assertIn("recovery coordinator", self.handoff()) + self.assertEqual(self.tree_snapshot(self.project), before) + self.assertEqual(sentinel.read_text(encoding="utf-8"), "incomplete bootstrap\n") + self.assertFalse(self.state.exists()) + self.assertIn("Observed lifecycle state: partial_bootstrap", result.stdout) + self.assertIn("Act as a fresh recovery coordinator", result.stdout) - def test_adopted_lockout_routes_to_dispatcher(self): + def test_bootstrap_mixed_with_established_lifecycle_fails_closed(self): + for lifecycle in ("active", "adopted", "retired"): + with self.subTest(lifecycle=lifecycle): + project = self.temp / f"project-{lifecycle}" + project.mkdir() + bootstrap = project / ".writwall-bootstrap" + bootstrap.mkdir() + (bootstrap / "HANDOFF.md").write_text( + "incomplete bootstrap\n", encoding="utf-8" + ) + governance = project / "governance" + if lifecycle == "active": + order = governance / "work-orders" / "WO-001.md" + order.parent.mkdir(parents=True) + order.write_text( + "---\nid: WO-001\nstatus: ACTIVE\n---\n", encoding="utf-8" + ) + pointer = project / ".claude" / "active-wo.txt" + pointer.parent.mkdir(parents=True) + pointer.write_text( + "governance/work-orders/WO-001.md\n", encoding="utf-8" + ) + else: + governance.mkdir() + for name in ("PLAN.md", "STATE.md", "ROUTING.md"): + (governance / name).write_text( + f"# {name}\n", encoding="utf-8" + ) + if lifecycle == "adopted": + decision = governance / "decisions" / "DR-001.md" + decision.parent.mkdir() + decision.write_text("# Adoption record\n", encoding="utf-8") + else: + closed = governance / "history" / "WO-001.md" + closed.parent.mkdir() + closed.write_text( + "---\nid: WO-001\nstatus: CLOSED\n---\n", + encoding="utf-8", + ) + + before = self.tree_snapshot(project) + result = self.run_lifecycle_start(project) + self.assertNotEqual( + result.returncode, 0, result.stdout + result.stderr + ) + self.assertEqual(self.tree_snapshot(project), before) + self.assertFalse(self.state.exists()) + self.assertIn("inconsistent state", result.stderr) + self.assertIn(".writwall-bootstrap", result.stderr) + + def test_adopted_lockout_routes_to_fresh_project_architect(self): governance = self.project / "governance" governance.mkdir() for name in ("PLAN.md", "STATE.md", "ROUTING.md"): @@ -485,10 +641,21 @@ def test_adopted_lockout_routes_to_dispatcher(self): decision = governance / "decisions" / "DR-001.md" decision.parent.mkdir() decision.write_text("# Adoption record\n", encoding="utf-8") - result = self.run_start() + before = self.tree_snapshot(self.project) + result = self.run_lifecycle_start() self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertEqual(self.intake()["observed_state"], "adopted_lockout") - self.assertIn("Act as Dispatcher", self.handoff()) + self.assertEqual(self.tree_snapshot(self.project), before) + self.assertFalse(self.output.exists()) + self.assertFalse(self.state.exists()) + self.assertIn("Observed lifecycle state: adopted_lockout", result.stdout) + self.assertIn("Act as a fresh Owner-Agent / Project-Architect", result.stdout) + flat = " ".join(result.stdout.split()) + self.assertIn("Recommendation and material tradeoff", flat) + self.assertIn("supporting evidence", flat) + self.assertIn("one combined disposition and action", flat) + self.assertIn("explicitly include creation and dispatch", flat) + self.assertIn("Do not ask for the same decision again", flat) + self.assertIn("perform every mechanically available authorized step", flat) def test_owner_time_yes_defines_capture_and_no_records_not_reported(self): yes = self.run_start("--owner-time", "yes") @@ -584,6 +751,14 @@ def test_clean_new_handoff_routes_bootstrap_charter_addendum(self): self.assertIn("Ordinary no-pointer work", handoff) self.assertIn("confers no mutation authority", handoff) + def test_clean_new_handoff_carries_terminal_fresh_architect_prompt(self): + result = self.run_start() + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + handoff = self.handoff() + self.assertIn("After adoption closeout", handoff) + self.assertIn(starter_module.PROJECT_ARCHITECT_PROMPT, handoff) + self.assertIn("onboarding coordinator stops", handoff) + def test_name_clearance_proof_tools_are_canonical_in_emitted_bundle(self): result = self.run_start() self.assertEqual(result.returncode, 0, result.stdout + result.stderr)