feat(carve S1): foundation contracts for the orchestration arc (additive, dormant) - #647
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #647 +/- ##
=======================================
Coverage ? 90.15%
=======================================
Files ? 241
Lines ? 59008
Branches ? 5477
=======================================
Hits ? 53199
Misses ? 5809
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ive, dormant) Slice 1 of the linear-vercel→main carve. Purely additive shared-contract changes that every later orchestration slice references — nothing here is wired or activated, so behavior on main is unchanged. - types.ts: additive optional TaskRecord fields the orchestration/Mode-B/A6 arc reads (orchestration_id, parent_task_id, depends_on, linear_issue_id, code_changed, head_sha, screenshot_url, answer_text, …). All optional → back-compatible. - validation.ts: attachment MIME maps + size constants. Deliberately keeps the max-turns default at main's value (the arc's bump to a higher default is a live behavioral change and belongs in its own PR, not this dormant slice). - workflows.ts: register the read-only planning + restack workflow definitions + the readOnly flag on the workflow type. Registry metadata only — the workflow definitions + planners land in later slices, unused until then. - repo-config.ts: additive optional build_command / lint_command on the repo + blueprint config (per-repo build/lint verification commands the agent runs before opening a PR; default to the platform commands when unset). Gates: cdk compile + eslint + full jest green. No new imports, no runtime wiring, no behavioral change.
Automated review — carve S1 (#647)Reviewed as its own diff only ( Slice standalone-ness: typecheck PASS, but the "dormant" claim does not fully hold
The other two files are not dormant, which is the substance of this review. 1. BLOCKING — two workflows are admitted here, but their agent YAML does not exist until #654This PR adds Verified per-slice:
Same for Failure: merge #647 or #653 alone and deploy. A user submits Fix: move the two 2. MAJOR — a live, security-relevant validation tightening ships here with its tests in #655
The 3. MAJOR — private work-item shorthand as the stated reason fields existThe new doc comments carry explanation-bearing private labels that resolve nowhere in the public repo — not at this slice and not at #662:
This PR is 4 files — it is the cheapest place in the stack to get this perfect, and it sets the tone for the other seven. Replace with what the field is for ("set when a task is spawned as part of a multi-issue breakdown"), and drop the labels. 4. MINOR —
|
The full-buffer UTF-8 check had no test for its failure branch: the existing cases only fed NUL bytes, which the null-byte scan catches on its own, so the `TextDecoder` throw path was never taken. Deleting the rejection entirely left the suite green. Three cases, each of which fails if the check regresses: - bytes that are not decodable UTF-8 but carry no NUL (a truncated multi-byte sequence, and 0xFF which never appears in UTF-8) must be rejected, - a 9 KB ASCII preamble must not launder trailing invalid bytes — this is what the whole-buffer check exists for, and it passes under a prefix-only scan, - legitimate multi-byte text must still be accepted, so the decode can't be "fixed" by rejecting everything non-ASCII.
|
Addressed the Codecov report above. The 2 uncovered lines were the I confirmed it was a genuine gap rather than a reporting artifact by mutation-testing it: deleting the rejection entirely left all 109 tests passing. The existing cases only fed NUL bytes, which the separate null-byte scan catches on its own, so the Added three cases, each verified to fail when the check is broken:
Re-running coverage, Note the remaining uncovered lines in that file are pre-existing and unrelated to this diff. Codecov also flags a missing BASE report for |
DESCRIPTORS is the live admission table — create-task-core resolves any submitted workflow_ref against it — so an entry here with no corresponding `agent/workflows/**` file is worse than a missing entry: the submission succeeds with a 201, the caller is told the task started, and the agent then dies when it cannot load the workflow. An accepted task that cannot run is a worse experience than a clean rejection at submission. `coding/restack-v1` and `coding/decompose-v1` were declared here while their YAML lands in a later change, and there is no platform-only submission gate to stop a user asking for them by id. Move both entries to travel with their files. The drift test only walked YAML → descriptor, which is why the orphans were invisible, so add the reverse direction: every declared descriptor must have a shipped workflow file. Verified by re-adding an orphan and watching it fail.
9d4914a to
111dcd6
Compare
Addendum — a committed symlink to the author's local filesystemFound while reviewing #653 (which deletes it). This is in this PR's diff and I missed it on the first pass. #647 commits Traced across the stack:
Two things follow:
Fix: drop the symlink from this commit. Optionally tighten |
Note: this PR was force-pushed during the review — status of each finding against the current headMy review above was written against
Everything else in the original review stands as written. |
Review findings addressedBLOCKING #1 — two workflows admitted before their definitions exist. Fixed. Confirmed independently: The root cause was the drift test: it walked YAML → descriptor only, so an orphan descriptor was invisible to it. Added the reverse direction — every declared descriptor must have a shipped workflow file — and verified it by re-adding an orphan and watching it fail. That guard is what stops this recurring, not the move. MAJOR #2 — the UTF-8 tightening shipped without its tests. Fixed (see the Codecov reply above). Three cases added, each mutation-verified to fail when the check is broken. Worth noting the coupling you identified was real in both directions: the later slice had its own equivalent tests, and its version covered a case mine missed (a multi-byte character straddling the old 8 KB cutoff), so I merged both rather than keeping just one. MAJOR #3 — private work-item shorthand as the stated reason fields exist. Fixed across this slice and the rest of the stack: tracker ids, MINOR #4 — MINOR #5 — NIT #6 — |
`node_modules/` with a trailing slash matches directories only, so a symlink of that name — e.g. one pointing at a shared install in a sibling worktree — is not ignored and `git add -A` will commit it. CI then fails at install time with `EEXIST: file already exists, mkdir '.../node_modules'`, which reads as a runner cache glitch rather than a tracked file. Match both forms, and drop the one that had been committed.
scottschreckengaust
left a comment
There was a problem hiding this comment.
Verdict: Approve with nits (posted as COMMENT — no code blockers, but the PR description materially misstates the diff)
Reviewing as a Principal AWS Solutions Architect. This is S1, the base of the 8-slice linear-vercel → main carve (tracking epic #668), a cross-repository (fork) PR from external author @isadeks. The code is clean, well-tested, and safe to land. My only substantive concerns are description/framing accuracy, not defects — hence COMMENT rather than a clean APPROVE.
Governance (ADR-003)
Gate is an approved backing issue. The feature issues this slice lands part of — #247 and #299 — both carry approved + P0. The tracking epic #668 is the correct place for the slice rationale. Backing-issue gate: satisfied. Branch name carve/s1-foundation-contracts does not match (feat|fix|chore|docs)/<issue>-..., but per the carve convention this is a de-facto-waived nit, not a blocker.
Vision alignment
Advances bounded blast radius: the slice is dormant-by-design (TypeScript compiles unimported library code; the orchestration path is env-gated in later slices), keeping main deployable while the arc lands reviewably. The validateMagicBytes change is a fail-closed tightening of the input gateway — squarely aligned with the admission-guard tenet. No tenet is traded away.
Blocking issues
None.
Non-blocking suggestions / nits
-
The PR body says
behavior on main is unchangedand calls the slicepurely additive … nothing is wired or activated— that is not accurate for two of the files, both of which are LIVE paths:cdk/src/handlers/shared/validation.ts—validateMagicBytesis called fromjira-attachments.tsandresolve-url-attachments.ts(live admission). Switching text validation from an 8 KB NUL-scan to a whole-bufferTextDecoder({fatal:true})+ full NUL scan is a real, live behavioral change. It is a strict tightening (a long ASCII preamble can no longer launder trailing binary; non-UTF-8 is now rejected), which is good — but it should be described as a live security fix, not a dormant contract. Note also thatdetectMimeTypeFromMagicBytes(validation.ts:390) still uses the 8 KB prefix scan (TEXT_MAGIC_BYTE_CHECK_BYTES); that is a detection heuristic rather than the validation gate, so it is defensible, but the two now use different rules — worth a one-line comment so a future reader does not "fix" the asymmetry.cdk/src/handlers/shared/workflows.ts— the body claims this fileregister[s] the read-only planning + restack workflow definitions + the readOnly flag on the workflow type. The actual diff adds only two model-allowlist entries (anthropic.claude-opus-4-8,us.anthropic.claude-opus-4-8);readOnlyand all fiveDESCRIPTORSalready exist onmain. Adding toWORKFLOW_MODEL_ALLOWLISTis itself a live policy change (it widens the set of models a submitted workflow may pin). Please correct the changelog so reviewers of later slices can trust the per-file descriptions.
-
Model-ID coverage (workflows.ts:89-90). The allowlist adds
anthropic.andus.anthropic.forms ofclaude-opus-4-8but not aglobal.anthropic.inference-profile form. If the deployment uses global cross-region inference profiles anywhere (the repo references aglobal.prefix elsewhere), a pinnedglobal.anthropic.claude-opus-4-8would be rejected. Confirm the intended prefix set. Non-blocking. -
repo-config.tsbuild_command/lint_command. Additive optional fields, fine for a dormant slice. When the orchestrator actually threads these into the agent payload (later slice), the repo-config docs underdocs/guides/should gain these knobs. Flag for the activation slice, not this one.
Documentation
No docs change is required for S1: all additions are optional persistence fields, an internal validation tightening, and allowlist metadata — none change a user-facing command or env var yet. No docs/guides / docs/design / Starlight-mirror edits are in the diff, so there is no mirror-sync obligation to check. The slice rationale is well-captured in #668. OK.
Tests & CI
- Bootstrap synth-coverage: N/A — the diff adds no CDK constructs/stacks/handlers and introduces no new CloudFormation resource types, so the ADR-002 bootstrap-policy checklist does not apply.
- New tests are solid and target the right trees (
cdk/test/handlers/shared/): the threevalidateMagicBytescases cover the non-UTF-8 reject, the 9 KB-preamble laundering reject, and the multi-byte-UTF-8 accept — i.e. failure paths, not just happy path. The newevery CDK descriptor has a shipped workflow filetest closes a real orphan-admission gap (accepted task that the agent then cannot run). I verified all five declared descriptor ids resolve to shippedagent/workflows/**YAMLs, so the test passes as written. Theid: '…'regex is anchored to 4-space indentation matching the currentDESCRIPTORSblock — a valid but slightly brittle source-scrape (it breaks if the table is ever reformatted or a nested 4-spaceid:appears); acceptable givenDESCRIPTORSis intentionally unexported. - I could not execute the jest suites in the review worktree (
node_modulesis not installed there andaws-cdk-lib/testhelpers/jest-autocleanis absent), so I verified the new tests by static analysis against source rather than by running them. CI on the PR is the authoritative gate.
Review agents run
- code-reviewer (style/guidelines): applied — code matches repo conventions; JSDoc is thorough; no routing issues (changes correctly land in
cdk/src/handlers/shared/). - silent-failure-hunter: applied — the one
catch {}invalidateMagicBytesdeliberately maps a decode failure toreturn false(fail-closed), which is correct and commented. No swallowed errors. - type-design-analyzer: applied — new
TaskRecordfields are optional and back-compatible;EXTENSION_TO_MIME/SUPPORTED_ATTACHMENT_EXTENSIONS_LABELare derived from the canonicalMIME_TO_EXTENSION(no drift by construction). Confirmed theseTaskRecordadditions are persistence-only and are not surfaced in any API response, so nocli/src/types.tssync is owed for this slice. - comment-analyzer: applied — this is where the PR-body/diff mismatch surfaced (nit #1). The in-file comments themselves are accurate.
- pr-test-analyzer: applied — see Tests section; coverage is appropriate for the live changes.
- /security-review: applied —
validation.ts(input gateway) and the model allowlist (policy gate) are in scope. Both changes are fail-closed / tightening; no IAM, Cedar, network, or secrets surface is touched. No findings.
Human heuristics
- Proportionality: pass — small, dormant foundation slice; complexity matches the carve strategy.
- Coherence: concern — the PR description's per-file claims do not match the
workflows.tsdiff and overstate dormancy forvalidation.ts(nit #1). The code itself is coherent. - Clarity: pass — names and JSDoc communicate intent well (
orchestration_id,depends_on,head_sha, etc. are precisely documented). - Appropriateness: pass — maintainable, derives reverse maps rather than duplicating, tests assert intended contract (reject laundered binary) not merely current behavior.
Net: safe to merge as part of the stack once the PR body is corrected. Approving in spirit; posting as COMMENT so the description-accuracy nits are addressed before the later, behavior-changing slices lean on this one.
… amounts Review raised that `validateMagicBytes` and `detectMimeTypeFromMagicBytes` now disagree about how much of the buffer to look at, with nothing explaining it — a reader is left to guess whether the 8 KB prefix scan in the detector is an oversight. It isn't, and a future "consistency" fix would make things worse. The detector's answer is a guess used to fill in a missing content type, and everything it returns is re-checked by `validateMagicBytes` over the whole buffer one call later. So scanning more bytes there buys no safety, while making it strict would turn a detection miss into a spurious rejection: it would return null for content that validation would have accepted. Also documents that the workflow model allow-list and the Bedrock IAM grant are independent lists, and that a model must be on both. Allow-listing alone lets a workflow pass admission and then fail at turn 0 with AccessDenied, which is a confusing place to learn about it. Adds a test that every bare allow-listed id is paired with its us- inference- profile form, and vice versa. An id admitted in only one form is a latent rejection, since admission compares the literal string a workflow pinned. Verified by deleting one pair and watching the assertion fail.
scottschreckengaust
left a comment
There was a problem hiding this comment.
Verdict: Approve with nits ✅
Re-review at head b619108a (the branch was reset/rebased and the PR body rewritten since my earlier COMMENT). This slice is a net security improvement with excellent tests and clean governance — it belongs. Two prior threads I raised are resolved (see below), and I found one new in-diff nit worth fixing plus a latent (pre-existing) gap the new comment inadvertently papers over.
Vision alignment
Advances the staged carve landing #247/#299 (both approved, P0) tracked under epic #668 (approved, P1). Bounded blast radius respected: the validateMagicBytes change is fail-closed (tightens admission), and the ~11 new TaskRecord fields are optional/back-compatible and provably dormant. Slicing keeps outcomes reviewable — this is exactly the "reviewable outcomes" tenet in action.
Prior threads — verified resolved
- validation.ts (live admission path): ✅ addressed. PR body now correctly labels this a live security tightening, and the hardening (full-buffer fatal UTF-8 + end-to-end NUL scan) is the right fix, with tests for the invalid-continuation-byte,
0xFF, 9000-byte-preamble laundering, and multi-byte-UTF-8-accept cases. - workflows.ts (2 allowlist entries only): ✅ addressed. Changelog corrected;
readOnly/DESCRIPTORSconfirmed already onmain. The bare↔us.pairing invariant now has a test in both directions.
Nits (non-blocking)
-
Inaccurate new comment claims a re-validation backstop that doesn't cover one path —
validation.ts(added lines ~388–390). The comment states a detected type is "handed straight toisAllowedMimeTypeand then tovalidateMagicBytes, which re-checks the WHOLE buffer. A wrong guess is caught one call later." That backstop does not run on the inline-attachment-without-content_typepath:validateAttachmentsgates the full check onif (decoded && att.content_type)(validation.ts:620), so an inline attachment with no declaredcontent_typegets its type fromdetectMimeTypeFromMagicBytes(8 KB-prefix NUL scan only, validation.ts:406) and is never run throughvalidateMagicBytes. Net effect: the very "8 KB clean ASCII + trailing binary" case the hardening targets can still be admitted on that path (bounded — inline data is ≤500 KB and still guardrail-screened downstream).- This gap is pre-existing (line 620 is not in this diff), so it's not a regression and not a blocker on this PR. But the PR's new comment asserts a safety property that isn't true. Suggested fix (cheap, and it makes the comment honest): drop the
&& att.content_typecondition at validation.ts:620 so inline detected types are also full-validated — closes the latent gap and makes the new comment accurate. Otherwise, soften the comment to stop claiming detection results are re-validated.
- This gap is pre-existing (line 620 is not in this diff), so it's not a regression and not a blocker on this PR. But the PR's new comment asserts a safety property that isn't true. Suggested fix (cheap, and it makes the comment honest): drop the
-
Doc example lists the wrong label order —
validation.ts(SUPPORTED_ATTACHMENT_EXTENSIONS_LABELcomment). Comment says"PNG, JPG, JPEG, TXT, ..."; the actual module-load value is"PNG, JPG, TXT, CSV, MD, JSON, PDF, LOG, JPEG"(thejpegalias is spread in last). Label is computed correctly; only the example is wrong. Thenew Set(...)dedup is redundant (object keys are already unique) but harmless. -
Newly-exported symbols have no consumers yet —
EXTENSION_TO_MIME,SUPPORTED_ATTACHMENT_EXTENSIONS_LABEL, and the newly-exportedMIME_TO_EXTENSION(validation.ts) have zero importers incdk/src,cli/src, or tests. Documented as scaffolding for later slices; acceptable for a staged carve, but it's speculative surface landing ahead of its code — and the derivation/label have no direct unit test (a future duplicate extension would silently collide inEXTENSION_TO_MIMEwith nothing to catch it). -
linear_issue_iddoc describes unwired behavior — types.ts documents it as "hoisted to top level ... at task-create time" for a GSI, but live code still reads/writes the nestedchannel_metadata.linear_issue_id(e.g.orchestrate-task.ts,linear-webhook-processor.ts); the top-level field is neither written nor read in this PR. Fine as forward-declaration, but the doc describes behavior that arrives in a later slice — consider a "wired in S_n" note like the orchestration fields have.
Documentation
No docs/guides or docs/design change required (internal contracts, dormant). .gitignore note (symlink node_modules) is well-explained. No Starlight mirror impact.
Tests & CI
CI green; PR reports 135 suites / 2509 tests. New tests are high-quality and cover the real risk paths (UTF-8 rejection, preamble laundering, multi-byte accept, orphan-descriptor admission, bare↔us. pairing). CDK bootstrap synth-coverage: not applicable — no new CloudFormation resource types (additive interface fields, allowlist strings, derived maps only). Minor gaps: empty-buffer text (currently returns true) and EXTENSION_TO_MIME/label are untested.
Review agents run
- code-reviewer / silent-failure-hunter (security lens on the admission path): run — surfaced the nit-1 re-validation gap; confirmed the decode is bounded by
MAX_ATTACHMENT_SIZE_BYTES(size checks are upstream in both hydration callers) and fail-closed. - type-design-analyzer: run — new optional fields are back-compatible; flagged
depends_onself-admitted dup-of-OrchestrationTable(dormant) and the coupled-optionals (screenshot_url/_preview_url,code_changed/answer_text) not expressed as invariants. - pr-test-analyzer: run — coverage assessed above.
- comment-analyzer: folded into nits 1, 2, 4 (comment accuracy).
- /security-review: covered by the admission-path pass above (no IAM/Cedar/network change in this diff).
- Omitted: none in scope.
Human heuristics
- Proportionality — pass. Change matches the problem; no over-abstraction.
- Coherence — pass. Fields/maps use consistent repo terms; parallel structure has real substance (tests, not boilerplate).
- Clarity — concern (nit 1): a security-path comment asserts a backstop that doesn't hold on one path. Names are otherwise clear.
- Appropriateness — pass. Maintainable; tests assert intended behavior (whole-buffer validation), not just current output.
|
Follow-up: cross-stack check of the new exports + the nit-1 validation gap (per reviewer request). I traced how the S1 additions are used by the downstream stacked PRs (checked at the current #662 tip). Two things worth recording:
Net: nit-1 remains a pre-existing, non-blocking gap localized to the legacy |
…detected Review found the magic-bytes check gated on a DECLARED content_type, so an inline attachment with no declared type — where the type is guessed by detectMimeTypeFromMagicBytes — never got the whole-buffer validation. That leaves the weaker of the two paths unguarded. Detection only scans an 8 KB prefix for NUL bytes, so it is a guess; a declared type at least states an intent. Reproduced before fixing: ~8 KB of clean ASCII followed by 0x00 0xFF 0xFE 0x00, submitted with no content_type, was guessed text/plain and ADMITTED. That is precisely the laundering the UTF-8 hardening was written to stop, reaching the same handlers through the door next to the one that was locked. Now every inline attachment is validated, detected types included. Confirmed this does not cost the detection path its purpose: a real PNG and real JSON with no declared content_type still resolve and pass, so the fix does not trade a laundering hole for spurious rejections. Full suite green with no other test needing a change, which is the useful signal here. Also corrects the comment on the detector. It asserted that whatever detection returns is re-checked whole-buffer by validateMagicBytes — true as a description of intent, false on this path until now. It now records that the re-check is load-bearing and that the call site must not be narrowed back to declared types. Verified by restoring the old gate and watching the laundering assertion fail.
|
@scottschreckengaust — your approval at Exactly one commit since your approval: I reproduced your case before changing anything: 8192 Two things I checked that seemed worth more than taking the fix on faith:
The comment that claimed the whole-buffer re-check happens was mine from the previous round, and you were right that it was false on that path. It now records that the re-check is load-bearing and that the call site must not be narrowed back to declared-types-only, since that is the shape the bug had. State: 0 unresolved threads, |
scottschreckengaust
left a comment
There was a problem hiding this comment.
Verdict: Approve ✅
Re-review at head 75a7a25b. Every substantive concern from the prior three rounds is now resolved in code — including the last one (the inline-attachment-without-content_type re-validation gap). I verified the fix independently and ran all in-scope review agents against this head. This slice is a net security improvement with strong tests, clean governance, and green CI. It belongs.
Prior thread — verified resolved
- Inline detected-type re-validation gap (
validation.ts:623): the guard is nowif (decoded)(wasif (decoded && att.content_type)), so detected MIME types are full-buffer validated too. I traced every path: wheneverdecodedis truthy,resolvedContentTypeis always assigned (declared or detected branch) beforevalidateMagicBytesis called — no undefined-type path. The laundering payload (8 KB ASCII + trailing binary, no declared type) is now rejected; genuinely-correct detected PNG/JSON still admit. Both directions are tested.
Vision alignment
Advances the staged carve landing #247/#299 (both approved, P0) under epic #668 (approved, P1). Bounded blast radius respected: the ~11 new TaskRecord fields are optional/back-compatible and provably dormant (they do not cross the toTaskDetail API projection), and the validateMagicBytes change is fail-closed. Slicing keeps outcomes reviewable.
Blocking issues
None.
Non-blocking nits
- Stale comment example (repeat nit, still unfixed) — cheap to fix here. The JSDoc above
SUPPORTED_ATTACHMENT_EXTENSIONS_LABELsays the label is"PNG, JPG, JPEG, TXT, CSV, MD, JSON, PDF, LOG", but the actual runtime value is"PNG, JPG, TXT, CSV, MD, JSON, PDF, LOG, JPEG"— thejpegalias is spread in last, soJPEGlands at the tail, not position 3. Code is correct; only the example is wrong. - No duplicate-extension collision guard.
EXTENSION_TO_MIMEinvertsMIME_TO_EXTENSIONviaObject.fromEntries; a future second MIME mapping to an existing extension would last-write-win silently with no compile error or test failure. A one-line invariant test (Object.values(MIME_TO_EXTENSION).length === new Set(...).size) would convert that latent drift into a build-time signal. - Coupled optionals on
TaskRecord(screenshot_url/screenshot_preview_url,code_changed/answer_text) admit illegal combinations. Appropriate for a dormant slice — defer tightening to a discriminated union until the writer slices land; a one-line "tighten when wired in S_n" breadcrumb would preserve the intent. - Benign empty-buffer case:
validateMagicBytes(Buffer.alloc(0), 'text/plain')returnstrue. Pre-existing, unreachable via detection, not a binary bypass (empty is valid text). No action needed.
Documentation
No obligation — the diff touches no docs/guides/, docs/design/, or CONTRIBUTING.md. Internal contracts, dormant fields, a validation tightening, and allowlist metadata; no Starlight mirror sync owed. Slice rationale captured in #668. The .gitignore symlink note is accurate.
Tests & CI
CI green (4/4). CDK bootstrap synth-coverage: N/A — no new CloudFormation resource types (additive fields, allowlist strings, derived maps only). New tests are behavioral and cover the real risk paths: UTF-8 continuation-byte reject, 0xFF reject, >8 KB preamble laundering reject, multi-byte UTF-8 accept, detected-path laundering reject + correct-detected-type admit, and the bare↔us. allowlist pairing invariant in both directions.
Review agents run (against head 75a7a25b)
- code-reviewer — routing correct; style consistent; confirmed no
cli/src/types.tssync owed (TaskRecordonCDK_ONLY_ALLOWLIST). - silent-failure-hunter (security lens) —
catch → return falseis correct fail-closed;fatal:truedecoder rejects overlong-NUL smuggling;resolvedContentTypealways assigned. No blockers. - type-design-analyzer — new fields back-compatible; flagged derived-map collision (nit 2) and coupled optionals (nit 3); no API leakage.
- comment-analyzer — 3 of 4 scrutinized comments accurate; caught the label-ordering inaccuracy (nit 1).
- pr-test-analyzer — no blocking coverage gaps; two optional nits.
- /security-review — folded into the admission-path pass; no IAM/Cedar/network/secrets surface touched.
- Omitted: none in scope.
Human heuristics
- Proportionality — pass. Small dormant slice; complexity matches the carve strategy.
- Coherence — pass. Consistent terms; derived maps rather than duplicated lists.
- Clarity — minor concern (nit 1): one JSDoc example is stale. The safety-property comments that failed prior rounds are now backed by the code.
- Appropriateness — pass. Maintainable; tests assert intended contract, not merely current behavior.
Net: safe to merge as part of the stack. Approving; the remaining nit is a trivial comment fix that needn't gate the merge.
Scope change: auto-decomposition is no longer part of this carveHeads-up before you re-read this — every slice was force-pushed, so a review in progress is against a stale diff. Sorry for the churn; the reason is a scope decision, not a fix. Auto-decomposition is staying on the development branch as experimental and is not landing on What moved~7,900 lines removed. 23 dedicated files, the agent workflow and its prompt, the admission-table entry, the CLI flags, and about 314 lines embedded in files that survive.
Three things worth knowing as a reviewerSome of the review fixes from the last round are gone with the feature. The verdict-marker fix (B1 on #662) and the retry-hint Two things looked decomposition-only and were not. Removing the feature exposed dead IAM. The reconciler held an Verification
|
Slice 1 of a staged carve — foundation contracts
First slice landing the Linear sub-issue orchestration + Mode-B + iteration arc (developed on the
linear-vercelline) ontomain, in dependency-ordered, independently-reviewable pieces. This slice is mostly additive shared-contract changes that later slices reference. Two exceptions, both flagged in review and both live policy changes onmain(see the correctedvalidation.ts/workflows.tsentries below): the text-attachment validation gate is tightened fail-closed, and the workflow model allow-list is widened. Everything else is unwired and inert.Contents
types.ts— additive optionalTaskRecordfields the arc reads (orchestration_id,parent_task_id,depends_on,linear_issue_id,code_changed,head_sha,screenshot_url,answer_text, …). All optional → back-compatible.validation.ts— attachment MIME maps + size constants, plus a live security tightening ofvalidateMagicBytes(not additive).mainscanned only the first 8 KB for NUL bytes and never validated UTF-8 despite its comment claiming it did; text attachments must now decode as valid, NUL-free UTF-8 end to end. Both callers (jira-attachments.ts,resolve-url-attachments.ts) exist onmain, so this is a live admission path: contentmainaccepts (an 8 KB ASCII preamble followed by binary) is now rejected.detectMimeTypeFromMagicBytesdeliberately keeps the cheap 8 KB prefix scan — it is a detection heuristic whose outputvalidateMagicBytesre-checks in full; a comment there explains why the two differ. (Deliberately keeps the max-turns default unchanged; the arc's bump to a higher default is a live behavioral change that will come as its own PR.)workflows.ts— two model allow-list entries only (anthropic.claude-opus-4-8and itsus.inference-profile form). An earlier version of this description claimed it registered the planning/restack workflow definitions and thereadOnlyflag; that was wrong — those already exist onmain. Adding toWORKFLOW_MODEL_ALLOWLISTis itself a live policy widening. Note the allow-list and the Bedrock IAM grant inbedrock-models.tsare independent lists and a model must be on both; the grant for this id lands in S3 (feat(carve S3): agent runtime — deterministic issue feedback, build/lint gate, restack workflow #654), so on S1/S2 the id is admissible but not invocable. Not reachable (the allow-list gates only models a workflow YAML pins, and none pin it on S1–S3) and documented at the list, with a test pinning the bare/us.pairing invariant.repo-config.ts— additive optionalbuild_command/lint_commandon the repo/blueprint config (per-repo build/lint verification commands; default to the platform commands when unset).Combination safety
Part of a staged sequence; every later slice is built + verified so that the union of all slices reproduces the deploy-verified
linear-verceltree. This slice's files are byte-identical to that source (comments aside).Gates
cdk compile + eslint + full jest (135 suites / 2509 tests) green.
🤖 Generated with Claude Code
Tracking
Slice S1 of the linear-vercel → main carve. Tracking issue: #668 (slice table, why it is sliced rather than merged, and review guidance).
Lands part of #247 (parent/sub-issue orchestration). Deliberately not
Closes— no single slice closes it; it closes when S8 activates the arc. The auto-decomposition issue (#299) is no longer part of this carve — that feature stays on the development branch as experimental.