feat: the carve estate reads chant's state manifests — the strangler fig survives a restart (#230 M3, chant#998) - #320
Merged
Merged
Conversation
…fig survives a restart (#230 M3, chant#998) The carve view's progress used to be whatever this tab remembered. chant 0.52.2 persists `<address>.carve.json` beside the emitted source — `carve emit` writes it, `carve bridge` and `carve apply` add their sections — so the progression already lives on disk, in chant's own file. behold now reads it. src/carve-manifest.ts is the reader: a v1 parse that refuses a major it does not speak, a bounded discovery walk, and the split that decides which side of the estate each ranked address draws on. Only `apply` is graduation — an `applied` address leaves the Terraform bands for the chant member box keeping its Terraform address (the morph's identity continuity), while `emitted` and `bridged` stay banded, repainted in the in-flight `accent` tone with the stage in words on the card. A card that read "carved" the moment source was emitted would claim an ownership transfer that has not happened. The chant panel's title carries the count (`carved so far — 1 of 12 carved`), the statusbar note says how many manifests the claim came from, and the scope panel lists each carve with its stage. A bare `behold carve report.json` with manifests beside it gets the same reading, with the graduated pile as its own band above the ranking; an ordinary `behold serve` whose project directory carries a carveout surfaces the state on `/api/project` with no report, no stepper and no demo scaffolding. Deliberately not joined onto that project's graph: the manifest keys on a Terraform address and those nodes key on chant entity names, which is #230's unsolved dedupe question. `carve apply` stays human. It graduates ownership, so there is no `/api/carve/apply` and no apply button — behold renders what the manifest records and echoes the retypeable command (no `--select`; the manifest names the target), exactly the posture the Handoff step takes with `terraform state rm`. The refusal is written down where the route and the runner would go, not left to be inferred from their absence. Fixtures are real: example-carve copied to a scratch dir, chant 0.52.2 pinned in its app/, and the three carve verbs run against legacy-tf — one manifest per stage, committed as chant wrote them, with the scratch path prefix normalized. The applied one was produced by running `chant carve apply` by hand, which is the only way that stage is ever reached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rfs1mLVWmwginHnDTg75sG
lex00
force-pushed
the
carve-m3-manifest-state
branch
from
August 30, 2026 04:38
370799b to
f74dbf0
Compare
lex00
added a commit
that referenced
this pull request
Aug 30, 2026
Two textual conflicts, both additive: - AGENTS.md — #323 documented the ops lens's per-rule cards on the same sentence this branch extended with `meta.operator`. Both statements kept, the rule table first (it is about the lens's own cards) and the operator strip after (it is about what rides beside them). - smoke/stub.mjs — #230 M3 and this branch each added state to `startStub`'s preamble. Both kept. Nothing semantic collided: #323 reads `args.rules` off the same op.json this branch reads `searchAttributes.Converge` off, and the two passes are independent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rfs1mLVWmwginHnDTg75sG
lex00
added a commit
that referenced
this pull request
Aug 30, 2026
… first cut, the strangler fig (#325) Since 0.11.0: the chant 0.52 line across root and every example (#316, chant 0.52.2's yaml fix in the locks), warm estate reads from a per-member IR cache (#312 → #307), the ops lens (#315) with its run playhead (#319) and ConvergeOp rule table (#323), the operator strip and gate approval card with approve's honest record-a-fact semantics (#324), disruption classes in the reconcile dial (#314), the helm drift axis (#318), manifest-driven carve state that survives a restart (#320, #230 M3), the observe {stacks} refusal and the pinned contrast floor (#313), and the CDK carve report compat pin (#317). pinhole 0.3.6 rides in the lock — the nested chant 0.44.14 is gone. Claude-Session: https://claude.ai/code/session_01Rfs1mLVWmwginHnDTg75sG Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes nothing on its own; this is M3 of #230 — TF as an estate member, the strangler-fig progress — built on the #254 walkthrough and the #283 estate frame.
What the manifest gives
chant 0.52.2 (chant#998, chant PR #1575) persists a carve state manifest beside the emitted source:
<address>.carve.jsonincarve emit --output, withcarve bridgeandcarve applyadding their own sections to the same file. It is the no-database progression source this milestone was gated on, and it is already on disk — behold just had to read it.The manifest's three sections are cumulative, so their presence is the stage:
emitemittedaccent, card readsemitted — not bridgedemit+bridgebridgedbridged — apply is yoursapplyappliedcarved → chantOnly
applyis graduation. A card that read "carved" the moment source was emitted would claim an ownership transfer that has not happened, which is the one class of claim behold's views exist to avoid. Both partial stages share the in-flight tone and are told apart by their word, which is on the card — tone never carries a state on its own here, and a second tone would have to borrow a token that already means something else in this picture.Where the progress lands:
carved so far — 1 of 12 carved, where the estate frame already summarizes that member/api/project'scarve.state, for agents:{manifests, progress, states[], apply: {human, note}}A resource that graduated in a previous session opens in the chant box; a partly-carved one opens back in its band saying how far it got. Nothing is inferred from what happened in the tab.
Real estates, not just the demo. A bare
behold carve out/report.jsonwith manifests beside it gets the same reading — the graduated pile becomes its own band above the ranking, since there is no chant member box to move a card into. An ordinarybehold serve <project>whose directory carries a carveout surfacescarve.stateon/api/projectwith no report, no stepper and no demo scaffolding, and grows nothing at all when the project was never carved. It is deliberately not joined onto that project's graph: the manifest keys on a Terraform address and those nodes key on chant entity names, and matching the two is the dedupe question #230's own tier grading called unsolved.The write boundary, restated
carve applygraduates ownership — it resolves the marker that makes chant the owner of a live resource. There is no/api/carve/applyand no apply button. behold renders what the manifest records about an apply and echoes the retypeable command (no--select; the manifest in the output dir already names the target), which is exactly the posture the Handoff step already takes withterraform state rm.runStepwould take the endpoint in four lines and every guard already written would still hold — which is why the refusal is written down where the route and the runner would go rather than left to be inferred from their absence:APPLY_IS_HUMANinsrc/carve-manifest.ts, and pointers to it insrc/carve-actions.ts,carveRoutesinsrc/server.ts, and the Handoff renderer inweb/carve-steps.js. The boundary is not that behold can't; it is that behold won't decide when an estate changes hands.Everything else is unchanged: emit and bridge still run only inside a
behold demo carvecopy, still write only intoapp/carveout/, still never pass--apply-rewrites. behold reads manifests and never writes one — the manifest is chant's file.Fixture provenance
src/__fixtures__/carve-manifests/holds three REAL manifests. This repo's ownexample-carvewas copied to a scratch dir (nothing in the checkout was touched), itsapp/installed with@intentius/chant@0.52.2+@intentius/chant-lexicon-aws@0.52.2and@cdktf/hcl2jsonbeside it, then the carve verbs run againstlegacy-tf/with--output app/carveout:aws_iam_role.api—carve emit→ emitted (and it carries chant'semit.params, the deferredname ← random_pet.suffixinput)aws_cloudwatch_log_group.worker— emit +carve bridge→ bridgedaws_s3_bucket.assets— emit + bridge +carve apply --env prod --stack assets --write --write-source→ appliedCommitted as chant wrote them, with one substitution: the scratch directory's absolute prefix was rewritten to
/tmp/carve-fixture, because the real one carried a session id and a home path and neither is part of the shape under test. The applied one exists only becausecarve applywas run by hand in a terminal — which is the only way that stage is ever reached.Verification
npm run tsc,npm run build,npm run smoke:ui— cleannpm test— 1175 passing (66 files), up from 1118 on mainbehold demo carve, ran Emit through the UI's own endpoint: the bucket repainted in-band asemitted — not bridged, statusbar readCarve state (from 1 manifest): 0 of 12 carved, 1 emitted — apply is yours. Ranchant carve applyby hand in the copy, restarted the server, and the card came back insideapp — chantwithcarved so far — 1 of 12 carved.POST /api/carve/apply→ 404.What M3 still lacks upstream
chant carve status --jsonover a tree would replace the walk with a contract.emit.filesandbridge.writtenare absolute paths chant resolved at run time. They read fine in the session that wrote them and are meaningless if the tree moves; behold trims them for display, which is presentation over a field that would be better relative to the manifest.TfNode.identityis the only handle and it is only populated for literal names, so a real project's graph still cannot colour a carved resource — the dedupe ask from design: a Terraform carve lens — chant carve advise as a scored overlay, TF as an estate member #230's tier grading is still open, and this PR deliberately stops short of guessing at it.carve applyis plan-only and BYOL by design, soappliedmeans "the marker is resolved and recorded", not "the cloud resource is tagged". The observe-side confirmation still depends on chant#1647's identity read path and the live tier.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rfs1mLVWmwginHnDTg75sG