feat: behold demo carve — the recordable carve walkthrough, offline tier (#254) - #272
Merged
Conversation
) One command from npm to a banded Terraform estate and a six-step stepper that carves a bucket across the line on camera: advise → pick → emit → bridge → handoff → done. No Docker, no cloud, no terraform binary. `behold demo carve` copies example-carve (#262), npm-installs the chant every step shells, drops @cdktf/hcl2json where chant's lazy import actually resolves it (the copy's ROOT node_modules — chant resolves from its own install upward, not from the .tf files), and runs `carve advise --report` over the copy. A boot that fails any of that serves the committed report and says so on the statusbar rather than drawing a blank graph. The stepper lives on a Carve tab mounted at runtime, so no other project grows a dead tab. Its two ACTION steps really run: POST /api/carve/emit shells `carve emit --state --select <addr>` and then `chant lint`, and renders the emitted source, chant's own boundary report and the lint verdict; POST /api/carve/bridge shells `carve bridge` WITHOUT --apply-rewrites and renders the proposed data source, the rewired survivors and the patch. Handoff is copy buttons and a paragraph about why it is not a button. Done marks the card chant-owned at the observe position. The write boundary (src/carve-actions.ts): the routes exist only in carve mode and act only when this server booted a demo copy; one directory is written (`<copy>/app/carveout`, inside the chant project so the emitted source's lexicon import resolves); the only request-derived value is `select`, and it must be an address the served report already ranks, so the argv element comes from a closed set read off disk. No cloud write, no Terraform mutation — verified by diffing the copy's legacy-tf after a full run. Emit reports `chant lint`, never `chant build` (chant#1637): the emitted bucket folds its sub-resources into the carve set without carrying them as native props, so build fails two policy rules on source scored 88. The step links the reason; the README and docs say it too. Tests: the actions and their refusals against a fake project-local chant in a temp copy (argv, the mkdir chant needs before --report, the artifact read-back, never --apply-rewrites), the route guards, the stepper's pure state machine, and a headless six-step walk in the UI smoke against a carve-mode stub serving the real committed report through the real lens. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aw report (#254) CI caught a race the local run never lost: `/api/carve` is a second fetch (it exists for the edge lists chant#1636 will publish), and a click that lands before it does left `cutSummary` with no resource — which made it claim "no boundary edges at all" for a bucket that has one. A different, wrong statement about the same resource. The counts are already on every card: the lens puts `inbound`/`outbound` in `attrs`. Read them there when the raw report isn't in hand, with `??` so a genuine 0 stays 0. The panel no longer waits for the fetch to say something true, and the smoke waits on a readiness marker so it tests the panel rather than the network. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tab's innerText (#254) The arithmetic line carries "1 inbound" too, so a substring test over the whole tab passed whether or not the cut summary rendered — and CI failing only the note next to it had no way to say which. Assert on .carve-cut and .carve-honesty directly, and print both when the note is missing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rough # Conflicts: # src/cli.ts
…itchable catalog row (#254) main moved the demo loader to an async `spawn` and grew a one-click catalog in the switcher. Two consequences for the walkthrough: - the carve boot's three steps (npm install, the HCL parser, `carve advise`) use the same non-blocking spawn, so cli.ts keeps no `spawnSync`; - `behold demo carve` is runnable anywhere but is not a project to switch INTO — carve mode claims /api/graph and /api/project at app creation, so a running server cannot become one. The row stays in the catalog marked `switchable: false` with the command that works, and POST /api/demos/open refuses it before the copy rather than after the install. Also: main's committed carve-report.json is now a chant#1636 one, so the smoke asserts the KNOWN path (the survivor named) and the counts-only fallback moves to the unit tests, which no longer depend on a fixture this repo ships. 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.
Part 2 of #254 (M1.5 of #230): one command from npm to the peel walkthrough. Builds on #258's carve lens, #262's estate, #264's JSON view, #257's sidecar write boundary and #268's demo catalog.
npx @intentius/behold demo carve # no Docker, no cloud, no terraform binaryWhat ships
behold demo carve— ademos.jsonentry (requires: []) that copiesexample-carvethrough #268's shared loader, npm-installs the chant every step will shell (app/), installs@cdktf/hcl2jsoninto the copy's rootnode_modules(chant lazy-loads the HCL parser with a bare import resolved from its own install upward, so that is where it resolves — not beside the.tffiles), and runschant carve advise --reportover the copy. A boot that fails any of that serves the committedcarve-report.jsonand puts the reason on the statusbar, the terminal and the Carve tab. A blank graph is the one outcome that never happens.The stepper — a Carve tab mounted at runtime (
panel.js's newaddPanelTab), so no other project grows a dead tab. Six steps on the deploy dial's own track, gated by real data dependencies rather than by wizard etiquette:POST /api/carve/emit→carve emit --state --select <addr>+chant lint; renders the emitted source, chant's boundary report (viajson-view) and the lint verdictPOST /api/carve/bridge→carve bridge, never--apply-rewrites; renders the data source, the rewired survivors and the patch✓ carved → chantmarker on the card + the end cardA finished run deliberately does not advance: the result is the step, and skipping past it would hide the thing the run was for. A new pick drops the previous runs — showing one resource's emitted source under another's name is the one way this panel could actively lie.
Honesty in the Pick step, both ways. With a chant#1636 report (the one committed on main now has the lists) it names the survivor the cut would strand and what bridging it costs. With a counts-only report — which is still what a real
behold demo carveregenerates, sinceexample-carve/apppins chant 0.44.4 — it reports the counts, names the cut from the breakdown, and says plainly that the survivors are not in this report. "None" and "not reported" stay different claims, and both paths are covered by tests.chant lint, notchant build(chant#1637) —carve emitfolds the bucket's versioning/public-access-block sub-resources into the carve set without carrying them as native props, sobuildfails two AWS policy rules on source the advisor scored 88. The Emit panel carries a "why lint and not build?" note; README,example-carve/README.mdand the docs page say it too.The write boundary (
src/carve-actions.ts)behold's second write, narrower than the first:
behold carve report.jsonshows the same six steps with the runs greyed out and a reason; an ordinary project serve doesn't register them at all (404).<copy>/app/carveout. It sits inside the chant project on purpose — the emitted source imports@intentius/chant-lexicon-awsand Node resolves that from the file's own directory upward, so a siblingcarveout/would failchant linton an install problem rather than on the source.select, and it must be an address the served report already ranks. The element that reaches the spawn's argv comes from a closed set read off disk, which is a stronger claim than any regex.carve bridgenever gets--apply-rewrites. Verified by diffing the copy'slegacy-tf/after a full run: identical.{error, code, remedy}refusals. AGENTS.md now states both exceptions and their exact size.Meeting #268
The walkthrough is runnable anywhere but is not a project to switch into — carve mode claims
/api/graphand/api/projectat app creation, so a running server cannot become one. The catalog row stays visible, markedswitchable: false, with the command that works on its face;POST /api/demos/openrefuses it before the copy and install rather than after.Tests
src/carve-actions.test.ts— the actions against a fake project-local chant installed into a temp demo copy: the argv built, the--outputdirectory created before chant's--reportneeds it (real chant writes the report first, verified against 0.44.4), the artifact read-back, path shortening, and a bin that exits non-zero if it ever sees--apply-rewrites. Plus the route guards: 404 off carve mode, 403 outside a demo, 400 on an unranked select, 415/413 on the body.web/carve-steps.test.js— the stepper's pure half: the gates,completedvs "walked past",cutSummary's two claims (including the fallback to the IR node's own counts),runbookCommands(drops annotations, keeps trailing comments, dedupes the runbook's twoterraform plans), andlintVerdictcounting warnings off chant's summary line rather than a column number.smoke/ui-smoke.mjs— a second stub in carve mode serving the real committed report through the real lens, with the two POSTs canned, so the six steps are deterministic in CI with no chant and no npm install. 30 new checks drive the whole walk headlessly.src/demos-route.test.ts/web/demos.test.js— the unswitchable catalog row and its early refusal.just checkgreen (979 tests),npm run smoke:uigreen.Also driven end to end in a browser against a real
behold demo carve— real chant 0.44.4, real emit, real lint, real bridge — which is how the auto-advance, the marker's position (measured, not read off attributes chant doesn't stamp) and a/api/resources500 on every carve-mode boot were all found.Deferred
--livetier (carve walkthrough: behold demo carve — the recordable peel-advise → carve experience (#230 M1.5) #254's second comment): scratch Floci in Docker,terraform applythe estate into it,lifecycle diff --liveafter Emit, a realterraform planshowing no destroy on camera. A follow-up.example-carve/app's pinned chant is bumped past chant#1636. The stepper reads both.behold carve <report.json>— the Terraform peelability report as a graph (#252) #258's carve mode is single-view:carveRoutesclaims/api/graphand/api/projectwholesale, and there is no seam for composing the report view with a chant member's graph. So this ships the honest minimum — the carve view with the walkthrough's demo context on it — and leaves serving the report inside an estate frame to the follow-up that adds the seam.#254 stays open for the live tier and the morph.
🤖 Generated with Claude Code