From 4cc5d943e37c955c6c4d6b2e672925189c7901a2 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:30:53 +0900 Subject: [PATCH 01/11] docs(interactive): freeze full delivery roadmap and closure ledger --- ...active-full-delivery-roadmap-2026-11-11.md | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 docs/interactive-full-delivery-roadmap-2026-11-11.md diff --git a/docs/interactive-full-delivery-roadmap-2026-11-11.md b/docs/interactive-full-delivery-roadmap-2026-11-11.md new file mode 100644 index 0000000..495307d --- /dev/null +++ b/docs/interactive-full-delivery-roadmap-2026-11-11.md @@ -0,0 +1,265 @@ +# FlowKit Interactive — Full Delivery Roadmap & Closure Ledger + +**Baseline date:** 2026-11-11 (UTC+8) +**Repository:** \`devcodex-labs/flowkit\` +**Frozen main:** \`ea2bb10893a9be2e5de3561fc9a660a6977ae15c\` +**Planning branch:** \`fix/m6-current-main-e2e-20261111\` + +> This document records the approved end-to-end route from the current M6/P0-A state through P0-B, P1, P2 and production delivery. It is a roadmap and traceability ledger, not evidence that deferred features have been implemented or passed. + +## 1. Current baseline + +### 1.1 Main and CI + +- Main HEAD: \`ea2bb10893a9be2e5de3561fc9a660a6977ae15c\`. +- Main CI run: [42954859078](https://github.com/devcodex-labs/flowkit/actions/runs/42954859078), completed successfully. +- M1–M5 implementation surfaces and S07 capacity work exist on main. Their presence is not a blanket claim that every M6/native-host gate is passed. +- PR #8, #9 and #10 are merged. +- PR #7 is open Draft, head \`e1e15a70356e53401288d393aa7527c6723fe797\`, based on an older main snapshot \`c873e3edf0f1a78b13178966cdeb408391755170\`. Its M6 tests need to be revalidated against current main. +- PR #12 is open Draft, head \`c6b42a36050d885a37831cbdf0839428b2052c85\`, based on \`ea2bb10893a9be2e5de3561fc9a660a6977ae15c\`. Its scripted C13–C20 tests and documentation CI passed; this does not pass C12/S13 native-host gates. + +### 1.2 Evidence classes + +- **P — Protocol/scripted:** real FlowKit compiler, generated Program, Host, SQLite and protocol; Agent responses may be scripted. Proves protocol behavior only. +- **M — Live model:** a real model session produces the result for an actual Pending. Requires per-turn request identity, response evidence and Host trace. +- **N — Native host attested:** native host tool invocation and session/process provenance are independently verified; OS/Host security boundary is demonstrated. +- **D — Deferred by approved scope:** intentionally belongs to a later milestone; it is not a defect in the current milestone, but must remain in the ledger. + +Never count P evidence as M or N. Never count a CI replay of recorded model turns as a new live model session. + +## 2. Complete milestone route + +| Phase | Scope | Required exit condition | Current state | +|---|---|---|---| +| 0 | Baseline freeze and traceability | Immutable baseline, full requirement/Finding/test ledger and evidence index | In progress on this branch | +| 1 | M6 current-main test compatibility | PR #7 tests run against frozen current main; no Core semantic change hidden in test repair | Pending | +| 2 | Trusted Host security design | Approved threat model, OS identity, IPC, key custody, Store ownership and provenance contract | Pending | +| 3 | Trusted Host security implementation | Windows and Linux isolation, access-denial tests and secure Host operations pass | Pending | +| 4 | Native Grok Build JS tool bridge | Grok Build itself calls FlowKit JS tools through Pending → model completion → JS submit/resume → Terminal | Pending | +| 5 | M6 live-model complex acceptance | Applicable C01–C24 cases classified and evidenced at P/M/N levels; all M6 blockers closed | Pending | +| 6 | M6 independent review and P0-A closure | Dot review, full CI, PR integration, all P0-A hard gates passed | Pending | +| 7 | P0-B sequential semantics | Action, Verification, Human, Loop, Cancel and context/result semantics pass S08–S12 and applicable T requirements | Deferred | +| 8 | P1 Parallel all_succeeded | Dedicated fail-fast/parallel contract, recovery and concurrency acceptance pass | Deferred | +| 9 | P2 Parallel any | Winner election, loser cancellation, late results and recovery pass | Deferred | +| 10 | Production compatibility and release | Host matrix, SDK/types/CLI/package/docs/performance/security/release gates all pass | Deferred | + +## 3. Phase 0 — Baseline and traceability + +1. Freeze main SHA, PR heads, dependency lock hash, Node/npm versions and CI run IDs. +2. Maintain a separate code branch for each implementation/fix; do not edit main directly. +3. Build a traceability index connecting T01–T34, S01–S14, C01–C24, N1–N13, R1/R2, R01–R22, source files, tests, CI jobs and evidence. +4. Every item has a state: PASS, FAIL, PARTIAL, BLOCKED or DEFERRED. +5. Every BLOCKED item must state the missing prerequisite, owner and re-entry condition. +6. Every DEFERRED item must name its target phase; it cannot disappear from the ledger. +7. Every PASS must cite an exact commit, executable test or raw evidence. A PR description alone is not evidence. + +## 4. Phase 1 — M6 current-main test compatibility + +1. Rebase/recreate the M6 test changes from current main, rather than relying on the stale PR #7 base. +2. Fix the temporary project fixture so the project-root contract is tested correctly: + - create a temporary \`package.json\` and verify default ancestor discovery; and + - separately verify explicit \`projectRoot\` override. +3. Include \`tests/integration/*.test.mjs\` in the normal test command. +4. Run the M6 cross-process tests and Linux OS-user isolation job. +5. Run build, full \`npm run verify\`, docs examples and website checks. +6. Keep the worker explicitly labelled scripted; do not present it as Grok/Codex/Claude provider integration. +7. If a production Core defect is found, create a separate minimal Core-fix PR and rerun relevant regressions. +8. Do not merge the old PR #7 or the replacement PR until review and exact-HEAD CI pass. + +## 5. Phase 2 — Trusted Host security design + +Freeze a Host threat model before implementation: + +- Assets: Host signing key, SQLite DB/WAL/SHM, Program allowlist, policy/binding manifests, private State, provider/session evidence. +- Principals: user, Grok Build/model process, Host process, other local processes, other tenants, CI and administrators. +- Trust boundaries: OS identity/token, filesystem ACL, IPC endpoint, provider/session transport and Host signer. +- Attack paths: same-user process, child process, shell/tool execution, path traversal/symlink, DB/WAL access, key extraction, IPC spoofing/replay, stale request, cross-tenant run, policy drift and log leakage. +- Host owns Program verification, authorization, private Store, signer and receipt acceptance. +- Model receives only the minimum public task and submits result data through a constrained operation. It never receives the signer, raw Store, Coordinator or unrestricted Host API. +- Define Windows and Linux deployment profiles separately. Linux UID isolation is not evidence of Windows ACL isolation. +- Define what can and cannot be proven about native provider/session origin. A self-declared session ID or Host signature over unverified model JSON is not provider-origin proof. +- Provider-signed evidence may be used if genuinely available, but is not assumed to be the only possible design. Any alternative Host-owned transport must state its exact assurance level and limits. +- Human approval remains unavailable/blocked unless a model-unwritable trusted human decision channel is independently verified. + +Deliverables: threat model, trust topology, IPC contract, key custody design, provider/session provenance design, Windows/Linux deployment designs, attack matrix and acceptance criteria. Independent review must close all design findings before Phase 3. + +## 6. Phase 3 — Trusted Host security implementation + +1. Implement distinct OS identities or an equivalently strong restricted token boundary. +2. Restrict Host private directory, key and SQLite DB/WAL/SHM to the Host principal. +3. Expose only constrained IPC operations with explicit authorization and input/output schemas. +4. Ensure the model cannot access raw DB, key, signer, Coordinator or private State. +5. Add Windows ACL and Linux UID/GID integration tests. A test that exits because the platform is unsupported is BLOCKED, not PASS. +6. Test read, write, replace, symlink, path traversal, process spawning, inherited handles/environment, IPC spoofing, replay, wrong tenant and unauthorized API calls. +7. Verify Host can still sign, commit, resume and recover after model access is denied. +8. Record raw OS identity, access-denied evidence, process tree, IPC identity and Store integrity. +9. Repeat after reboot/restart and on the supported Node matrix. + +Exit: security tests pass on both supported OS families, with no same-principal bypass. If the actual Grok Build execution environment cannot be isolated from Host secrets, keep native secure-host acceptance BLOCKED and do not expose Host secrets to that model. + +## 7. Phase 4 — Native Grok Build JavaScript bridge + +1. Use direct Grok Build as the first native host; ACP is optional and not a prerequisite. +2. Verify the real Grok Build process/session and available JS execution/tool interface. +3. Register or expose the FlowKit JS tool through the supported native tool mechanism; do not substitute file polling or an external harness that advances every step. +4. Freeze tool operations and schemas (names to be reconciled with the existing SDK contract): start, get Pending, submit result, resume, status and result. +5. The Host owns Program execution, Pending creation, disclosure, result verification, signing, CAS and SQLite. +6. Grok Build must call the JS tool, inspect the returned Pending, perform the real Agent work, call the JS tool to submit, and call again to continue until Terminal. +7. Preserve raw native tool invocation events and bind them to runId/nodeId/requestId/attempt/programHash/response digest and Host acceptedRevision. +8. Verify that a model cannot forge Host provenance by editing a session ID, JSON file or self-asserted receipt. +9. Keep provider-origin assurance distinct from Host-observed process/transport assurance; document any residual limitation. +10. Do not implement a Grok-only workflow semantic. The adapter is host-specific; FlowKit Core remains model-neutral. + +Exit: repeatable native tool loop, Host-controlled transitions, secure process boundary and independently reviewable evidence. + +## 8. Phase 5 — M6 complex live-model acceptance + +- Preserve the frozen C01–C24 definitions and original pass/fail criteria. +- For each scenario, declare required evidence tier P, M or N. +- Reuse existing P-level tests; do not waste model calls repeating scripted cases. +- Live-model tests must include real Pending, model output, request identity, tool trace and Host acceptance. +- Native-host/security tests require N evidence; a real model answer alone is not enough. +- Prioritize C04, C05, C06, C07, C08, C09, C12, C22 and C23. +- C05: kill/restart around Pending publication, model completion, receipt acceptance, validation, State commit and next Pending; resume the same Run. +- C06: two independent live model sessions contend for the same Pending; identical replay and conflicting result cases. +- C04: separate controlled invalid-output injection from naturally generated invalid model output and retry exhaustion. +- C09: distinguish unverified oversized receipt, trusted provider output overflow and valid receipt causing Checkpoint capacity overflow. +- C22: prompt injection and attempts to read/modify Host assets. +- C23: a combined multi-Agent Run with failure, retry, Host crash/recovery and competing submitters, with exact state/ledger invariants. +- C21 cross-OS/Node DB migration remains an enhancement unless the approved contract elevates it; N6 durable artifact/SDK upgrade compatibility must still be closed before long-running production delivery. + +Exit: every C item is PASS, FAIL, PARTIAL, BLOCKED or explicitly DEFERRED with owner and re-entry condition. No missing row. + +## 9. Phase 6 — M6 review, P0-A closure and merge + +1. Dot independently checks exact code SHA, Program hash, raw native tool events, Pending/response, Host traces, signed statements, SQLite, Journal hash chain and OS evidence. +2. Run exact-HEAD CI: Ubuntu 24.04 and Windows 2025 × Node 22.13.0 and 24.22.1. +3. Run Linux cross-UID and Windows ACL isolation jobs independently. +4. Run build, npm verify, package/tarball, types, CLI, docs examples, website and Standard regressions. +5. Resolve every M6 blocker and rerun the relevant scenario plus full regression. +6. PR #12 may be merged only as the scripted matrix/docs contribution after its own review; its merge does not pass C12/S13. +7. PR #7 or its current-main replacement may be merged only after M6 integration CI and security gates pass. +8. Do not release a version until P0-A release criteria are explicitly met. + +## 10. Phase 7 — P0-B sequential workflow + +Before implementation, close applicable design findings and freeze semantics for: +- N2: kind-specific Pending with model-visible task and trusted executor context. +- N4: Human provenance; model-generated human approval is rejected. +- N7: stable host-visible terminal result/state API. +- N13: Standard/Interactive semantic-difference register. +- N5: namespace/tenant isolation. +- N6: immutable artifact retention and SDK/semantics-version upgrade policy. +- N8/N9/N10/N11/N12 as applicable to Program ABI, feature gate, package contract, Agent Pending parity and transition/reconcile identity. + +Implement and validate in this order: +1. Action Patch and writes allowlist. +2. Action receipt loss, indeterminate and reconcile with stable operationId. +3. Verification true/false/invalid and candidate-state semantics. +4. Action → Verification → onFailure paths and contextError persistence. +5. Human reject → revise → approve with trusted human provenance. +6. Loop iteration limits, final failure and restart recovery. +7. Skill pinning/drift behavior. +8. Cancel, terminal race and late receipt rejection. +9. Stable terminal result and contextError reads. +10. Standard/Interactive T33 row-by-row parity/difference review. + +Exit: applicable T15–T23, S08–S12, T31/T32 and T33 checks pass. Any unsupported host Human channel stays BLOCKED, not silently simulated. + +## 11. Phase 8 — P1 Parallel all_succeeded + +Before code, separately approve the P1 contract and fail-fast semantics (historical TD-017): +- multiple Pending creation and durable branch identity; +- branch scheduling and dispatch barriers; +- all_succeeded aggregation; +- one branch failure and other in-flight branch behavior; +- stop-new-dispatch versus already-dispatched work; +- state write-set conflict and merge policy; +- duplicate/late receipts; +- crash/restart with partially completed branches; +- external side-effect reconciliation (no false exactly-once or rollback claim). + +Implement Program/IR, Coordinator, Store and Host support, then test independent branches, simultaneous success/failure, CAS races, recovery and Standard parity. Do not implement P2 any semantics in this phase. + +## 12. Phase 9 — P2 Parallel any + +Freeze and implement separately: +- deterministic winner election when multiple branches succeed; +- atomic terminal CAS; +- loser cancellation and already-running side effects; +- late result rejection/ack semantics; +- write conflicts and winning-branch state; +- crash/restart during winner election and cancellation; +- external side-effect reconciliation. + +Do not infer any semantics from all_succeeded or claim that cancelling a local process rolls back an external operation. + +## 13. Phase 10 — Production compatibility and release + +1. Host adapters: ChatGPT, Codex, Claude Code and Grok Build, each with a declared capability/limitation matrix. +2. Core remains model/host neutral; adapters do not change workflow semantics. +3. Verify SDK JS API, TypeScript declarations, exports, CLI, package/tarball and supported Node versions. +4. Verify immutable Program ABI, artifact storage, resume after SDK update and semantics-version compatibility. +5. Complete install, quick-start, DSL, Agent, Host, security, recovery, Action/Human/Loop/Parallel, error and API docs. +6. Verify docs against source and executable examples. +7. Run performance/stability tests: long chains, many Runs, concurrency, WAL/FULL, Checkpoint size, Journal growth, recovery loops and large outputs. +8. Complete security review, changelog, SemVer, tag, npm release, website and release notes. +9. Re-run installation/upgrade verification against the published artifact. + +## 14. Global traceability ledger + +### 14.1 Frozen requirement and acceptance identifiers + +| Family | Coverage | Closure rule | +|---|---|---| +| T01–T14 | P0-A core semantics | P0-A hard gates | +| T15–T23 | P0-B sequential semantics | P0-B hard gates | +| T24, T26 | P1 parallel all_succeeded/concurrency | P1 hard gates | +| T25 | P2 parallel any | P2 hard gates | +| T27–T34 | host, process, compatibility, parity and long-run requirements | assigned to the relevant phase; no silent omission | +| S01–S07 | P0-A protocol/security/capacity | P0-A | +| S08–S12 | P0-B Action/Verification/Human/Loop/Cancel | P0-B | +| S13 | native host loop | Phase 4 / M6 | +| S14 | package, types, CI, compatibility and Standard regression | every release gate | +| C01–C24 | current complex acceptance matrix | Phase 5, tier P/M/N explicit | +| N1–N13 | design review findings | closed or explicitly deferred before dependent phase | +| R1/R2, R01–R22 | historical review/falsification cases | preserved as regression and review evidence | + +### 14.2 Finding ownership + +| Finding | Planned closure | +|---|---| +| N1 | verify current P0-A deterministic-error exit; fix if any discrepancy | +| N2 | Phase 7 P0-B kind-specific Pending/executor context | +| N3 | verify current Program ABI/InteractiveRuntime; close any remaining contract gap before P0-A release | +| N4 | Phase 7 Human provenance; shell-only model approval remains blocked | +| N5 | Phase 7 tenant/namespace boundary before production multi-tenant use | +| N6 | Phase 7 durable artifact and SDK upgrade/resume policy | +| N7 | Phase 7 public host-visible terminal result | +| N8 | verify generated Program zero-import rule before release | +| N9 | verify unsupported featureStage rejection before release | +| N10 | package/bin/exports checks before release | +| N11 | Agent Pending parity before P0-A final compatibility sign-off | +| N12 | parallel/transition/reconcile identity before P1 | +| N13 | Standard/Interactive difference register before P0-B parity sign-off | + +This assignment is a tracking map, not an assertion that the finding is already fixed or that a new API is approved. Any requirement-stage change requires the user's approval. + +### 14.3 Global release rule + +No stage is complete while any applicable requirement, acceptance case, historical Finding or regression remains unclassified. Every unresolved item must be either: +- fixed and re-tested; +- BLOCKED with concrete missing evidence and re-entry condition; or +- DEFERRED to an approved later phase with a named milestone. + +A phase cannot pass by deleting a test, weakening an assertion, changing the evidence tier or combining results from different commits. + +## 15. Current next actions + +1. Complete Phase 0 baseline/traceability. +2. Port PR #7 M6 integration tests onto the frozen current main in a new branch. +3. Fix project-root fixture correctly and add default-discovery plus explicit-override tests. +4. Re-run exact-HEAD M6 integration, full verification and platform CI. +5. Independently review and then proceed to Phase 2 Trusted Host security design. + +**No merge, main modification, release or production semantic change is authorized by this roadmap.** From 290033c6cec8c133c04d2fed81be6aa3b6d1d982 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:32:41 +0900 Subject: [PATCH 02/11] test(interactive): port M6 Host integration onto current main --- tests/integration/m6-e2e.test.mjs | 380 ++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 tests/integration/m6-e2e.test.mjs diff --git a/tests/integration/m6-e2e.test.mjs b/tests/integration/m6-e2e.test.mjs new file mode 100644 index 0000000..6c1de14 --- /dev/null +++ b/tests/integration/m6-e2e.test.mjs @@ -0,0 +1,380 @@ +import test,{after} from 'node:test'; +import assert from 'node:assert/strict'; +import { + mkdtemp,mkdir,writeFile,readFile,chmod,rm, +} from 'node:fs/promises'; +import {generateKeyPairSync} from 'node:crypto'; +import {spawn} from 'node:child_process'; +import {tmpdir} from 'node:os'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {compileInteractive,createSqliteCheckpointStore} from '../../src/interactive/index.mjs'; +import {canonicalBytes,sha256Bytes} from '../../src/interactive/canonical.mjs'; +import {run,isControlSignal,evalWhen} from '../../src/index.mjs'; + +const hostScript=fileURLToPath(new URL('./m6-host.mjs',import.meta.url)); +const workerScript=fileURLToPath(new URL('./m6-worker.mjs',import.meta.url)); +const roots=[]; +after(async()=>{for(const root of roots)await rm(root,{recursive:true,force:true})}); + +const workflowText=[ + "import {defineWorkflow,condition,node,terminal} from '@devcodex-labs/flowkit/dsl';", + "export default defineWorkflow({id:'m6-acceptance',version:'1.0.0',name:'Acceptance',nodes:[", + "condition('gate',{name:'Gate',branches:[{when:'ready',next:'worker'},{when:'true',next:'declined'}]}),", + "node('worker',{name:'Worker',skill:'skills/writer.md',inputs:['message'],", + " output:{type:'object',properties:{answer:{type:'string'}}},retries:2,next:'done'}),", + "terminal('done',{name:'Done',status:'succeeded'}),", + "terminal('declined',{name:'Declined',status:'failed'})]});", +].join('\n'); + +const privateSentinel='HOST_PRIVATE_SENTINEL'; +async function fixture(t,{stageBudget=128,ready=true,allowedInputKeys=['message'],osIsolation=false}={}) { + const root=await mkdtemp(path.join(tmpdir(),'flowkit-m6-realhost-')); + roots.push(root); + // The current compiler discovers the nearest package.json by default. + // Keep the fixture self-contained instead of relying on the caller's cwd. + await writeFile(path.join(root,'package.json'),JSON.stringify({ + name:'flowkit-m6-fixture',private:true,type:'module', + })+'\n',{mode:0o644}); + await chmod(root,0o755); // allow low-privilege model worker to traverse public ancestors + const privateRoot=path.join(root,'host-private'); + const skillDir=path.join(root,'skills'); + await mkdir(privateRoot,{mode:0o700}); + await mkdir(skillDir,{mode:0o755}); + await chmod(privateRoot,0o700); + const skillPath=path.join(skillDir,'writer.md'); + await writeFile(skillPath,'Review and return a JSON answer.',{mode:0o644}); + const sourcePath=path.join(root,'acceptance.workflow.js'); + await writeFile(sourcePath,workflowText,{mode:0o644}); + // The isolated nobody worker cannot traverse the CI checkout on every + // runner. Publish *only* this non-secret scripted worker into a world- + // readable temp root, while Host DB/keys remain under privateRoot/0700. + const publicWorker=path.join(root,'public-m6-worker.mjs'); + await writeFile(publicWorker,await readFile(workerScript),{mode:0o644}); + await chmod(publicWorker,0o644); + const compiled=await compileInteractive(sourcePath,{ + output:'memory',featureStage:'P0-A', + }); + assert.equal(compiled.diagnostics.filter(x=>x.severity==='error').length,0, + JSON.stringify(compiled.diagnostics)); + assert.ok(compiled.programIdentity?.artifactHash); + const identityPath=path.join(privateRoot,'manifest.json'); + const programPath=path.join(privateRoot,'accepted.interactive.mjs'); + const storePath=path.join(privateRoot,'run.db'); + const privateKeyPath=path.join(privateRoot,'signing-key.pem'); + const configPath=path.join(privateRoot,'host-config.json'); + const pair=generateKeyPairSync('ed25519'); + await writeFile(programPath,compiled.code,{mode:0o600}); + await writeFile(identityPath,JSON.stringify(compiled.programIdentity),{mode:0o600}); + await writeFile(privateKeyPath, + pair.privateKey.export({type:'pkcs8',format:'pem'}),{mode:0o600}); + const config={ + namespace:'m6-reference-host',runId:'run-m6-test', + startKey:'m6-acceptance-start',storePath,programPath,identityPath, + privateKeyPath,skillPath,sourcePath,skillHash:sha256Bytes(await readFile(skillPath)),workerPath:publicWorker, + state:{ready,message:'hello',secret:privateSentinel}, + allowedInputKeys,stageBudget,osIsolation, + }; + await writeFile(configPath,JSON.stringify(config),{mode:0o600}); + await chmod(privateRoot,0o700); + const readStore=()=>{ + const store=createSqliteCheckpointStore(storePath,{namespace:config.namespace}); + try { + const cp=store.load(config.runId); + return {cp,journal:store.listJournal(config.runId), + artifact:cp&&store.getArtifact(cp.artifactHash)}; + } finally {store.close()} + }; + return { + root,sourcePath,configPath,config,compiled,readStore, + async updateConfig(update) { + Object.assign(config,update); + await writeFile(configPath,JSON.stringify(config),{mode:0o600}); + }, + async standardResult() { + const host={ + isControlSignal,evalWhen, + runAgent:async step=>({answer:'external-worker-processed:'+step.input.message}), + validateResult:(_schema,result)=>typeof result?.answer==='string'?[]:['bad answer'], + }; + return run(sourcePath,host,{...config.state},{projectRoot:root,skillCheck:true}); + }, + }; +} +function readEvents(stdout) { + return stdout.trim().split(/\r?\n/).filter(Boolean).map(line=>{ + let event=JSON.parse(line); + if(typeof event.event!=='string') throw new TypeError('Malformed Host event'); + return event; + }); +} +async function invoke(fixture,mode='start',cut='none') { + return new Promise((resolve,reject)=>{ + const child=spawn(process.execPath,[hostScript,fixture.configPath,mode,cut], + {stdio:['ignore','pipe','pipe']}); + let stdout='',stderr='',length=0; + const timeout=setTimeout(()=>{child.kill();reject(new Error('M6 Host timeout'))},60000); + child.stdout.on('data',b=>{ + length+=b.length; + if(length>512*1024) {child.kill();reject(new RangeError('Host trace exceeds limit'))} + else stdout+=b.toString(); + }); + child.stderr.on('data',b=>{if(stderr.length<2048)stderr+=b.toString().slice(0,2048-stderr.length)}); + child.on('error',e=>{clearTimeout(timeout);reject(e)}); + child.on('close',(code,signal)=>{ + clearTimeout(timeout); + try { + const events=readEvents(stdout); + resolve({code,signal,events,stderr,stdout}); + } catch(e) {reject(e)} + }); + }); +} +function assertContinuousJournal(snapshot) { + assert.deepEqual(snapshot.journal.map(item=>item.revision), + Array.from({length:snapshot.cp.revision+1},(_,i)=>i)); + assert.equal(snapshot.journal.at(-1).checkpointHash, + sha256Bytes(canonicalBytes(snapshot.cp))); + for(let i=1;i{ + const root=await mkdtemp(path.join(tmpdir(),'flowkit-m6-project-root-')); + roots.push(root); + const projectRoot=path.join(root,'project'); + const sourceDir=path.join(root,'workflow-source'); + const skillDir=path.join(projectRoot,'skills'); + await mkdir(skillDir,{recursive:true}); + await mkdir(sourceDir,{recursive:true}); + await writeFile(path.join(projectRoot,'package.json'),JSON.stringify({ + name:'flowkit-m6-explicit-root',private:true,type:'module', + })+'\n',{mode:0o644}); + await writeFile(path.join(skillDir,'writer.md'),'Review and return a JSON answer.',{mode:0o644}); + const sourcePath=path.join(sourceDir,'acceptance.workflow.js'); + await writeFile(sourcePath,workflowText,{mode:0o644}); + const compiled=await compileInteractive(sourcePath,{ + output:'memory',featureStage:'P0-A',projectRoot, + }); + assert.equal(compiled.diagnostics.filter(x=>x.severity==='error').length,0, + JSON.stringify(compiled.diagnostics)); + assert.ok(compiled.programIdentity?.artifactHash); +}); + +test('M6: real compiler artifact -> Node Host -> separately spawned worker -> authenticated receipt -> terminal matches Standard',async t=>{ + const f=await fixture(t,{osIsolation:process.env.FLOWKIT_M6_ISOLATION==='1'}); + const host=await invoke(f); + assert.equal(host.code,0,host.stderr); + assertModelNeverSawSecret(host); + assert.deepEqual(host.events.map(x=>x.event),[ + 'host-start','task-published','worker-exited','receipt-accepted','host-terminal', + ]); + assert.equal(host.events[0].status,'waiting'); + assert.equal(host.events.at(-1).status,'succeeded'); + const persisted=f.readStore(); + assertContinuousJournal(persisted); + assert.equal(persisted.cp.status,'succeeded'); + assert.equal(persisted.cp.state.secret,privateSentinel); + assert.equal(persisted.cp.state.answer,'external-worker-processed:hello'); + assert.equal(persisted.cp.receipts.length,1); + assert.equal(persisted.cp.receipts[0].acceptedRevision, + host.events.find(x=>x.event==='receipt-accepted').acceptedRevision); + assert.equal(persisted.cp.artifactHash,f.compiled.programIdentity.artifactHash); + assert.ok(persisted.artifact.bytes.equals(Buffer.from(f.compiled.code))); + const standard=await f.standardResult(); + assert.equal(standard.terminal,'done'); + assert.equal(sha256Bytes(canonicalBytes(standard.state)), + sha256Bytes(canonicalBytes(persisted.cp.state))); + const replay=await invoke(f,'resume'); + assert.equal(replay.code,0); + assert.deepEqual(replay.events.map(x=>x.event),['host-start','host-terminal']); + assert.equal(replay.events.at(-1).revision,persisted.cp.revision); +}); + +test('M6: after Pending publish, killing Host and reopening only persisted Program/Store preserves requestId exactly',async t=>{ + const f=await fixture(t,{osIsolation:process.env.FLOWKIT_M6_ISOLATION==='1'}); + const killed=await invoke(f,'start','after-pending'); + assert.notEqual(killed.code,0,'fault must terminate Host'); + assert.ok(killed.events.some(e=>e.event==='fault-injected')); + assertModelNeverSawSecret(killed); + const prior=f.readStore(); + assert.equal(prior.cp.status,'waiting'); + assert.equal(prior.cp.pending.requestId, + killed.events.find(e=>e.event==='task-published').requestId); + const revision=prior.cp.revision; + // No source DSL is needed for resume: Program bytes are retained in Store. + await rm(f.sourcePath); + const resumeStarted=process.hrtime.bigint(); + const recovered=await invoke(f,'resume'); + const resumeMs=Number(process.hrtime.bigint()-resumeStarted)/1e6; + assert.equal(recovered.code,0,recovered.stderr); + assert.equal(recovered.events.find(x=>x.event==='task-published').requestId, + prior.cp.pending.requestId); + assert.ok(recovered.events.find(x=>x.event==='receipt-accepted').acceptedRevision>revision); + const done=f.readStore(); + assert.equal(done.cp.status,'succeeded'); + assert.equal(done.cp.receipts.length,1); + assertContinuousJournal(done); + console.log('M6_RECOVERY_METRICS '+JSON.stringify({ + schemaVersion:1,resumeMs:Math.round(resumeMs), + revisionBefore:revision,revisionAfter:done.cp.revision, + casCount:done.cp.revision,journalEntries:done.journal.length, + checkpointBytes:canonicalBytes(done.cp).byteLength, + sameRequestId:true, + })); +}); + +test('M6: worker finished but Host crashed before durable receipt; retry preserves Pending identity, not exactly-once provider execution',async t=>{ + const f=await fixture(t); + const killed=await invoke(f,'start','after-worker'); + assert.notEqual(killed.code,0); + assert.ok(killed.events.some(e=>e.event==='worker-exited')); + const before=f.readStore(); + assert.equal(before.cp.status,'waiting'); + assert.equal(before.cp.receipts.length,0); + const sameRequest=before.cp.pending.requestId; + const resumed=await invoke(f,'resume'); + assert.equal(resumed.code,0,resumed.stderr); + assert.equal(resumed.events.find(x=>x.event==='task-published').requestId,sameRequest); + assert.equal(f.readStore().cp.receipts.length,1); + assert.equal(f.readStore().cp.state.answer,'external-worker-processed:hello'); +}); + +test('M6: budget 1 versus budget 128 reaches byte-equal terminal State, with monotonic CAS and no fake waiting',async t=>{ + const a=await fixture(t,{stageBudget:1}); + const b=await fixture(t,{stageBudget:128}); + const startClock=process.hrtime.bigint(); + const [fast,slow]=await Promise.all([invoke(a),invoke(b)]); + const durationMs=Number(process.hrtime.bigint()-startClock)/1e6; + assert.equal(fast.code,0,fast.stderr); + assert.equal(slow.code,0,slow.stderr); + for(const f of [a,b]) assertContinuousJournal(f.readStore()); + const left=a.readStore(),right=b.readStore(); + assert.equal(sha256Bytes(canonicalBytes(left.cp.state)), + sha256Bytes(canonicalBytes(right.cp.state))); + assert.equal(left.cp.status,'succeeded'); + assert.equal(right.cp.status,'succeeded'); + assert.ok(left.cp.revision>=right.cp.revision); + for(const run of [fast,slow]) { + assert.equal(run.events.filter(x=>x.event==='task-published').length,1); + assert.equal(run.events.at(-1).status,'succeeded'); + } + console.log('M6_S07_METRICS '+JSON.stringify({ + schemaVersion:1,wallMs:Math.round(durationMs), + budget1:{checkpointBytes:canonicalBytes(left.cp).byteLength,casCount:left.cp.revision, + journalEntries:left.journal.length}, + budget128:{checkpointBytes:canonicalBytes(right.cp).byteLength,casCount:right.cp.revision, + journalEntries:right.journal.length}, + equalTerminalState:true, + })); +}); + +test('M6: changed Host disclosure policy denies resume with no journal/CAS mutation',async t=>{ + const f=await fixture(t); + const killed=await invoke(f,'start','after-pending'); + assert.notEqual(killed.code,0); + const original=f.readStore(); + const bytes=canonicalBytes(original.cp); + await f.updateConfig({allowedInputKeys:[]}); + const denied=await invoke(f,'resume'); + assert.notEqual(denied.code,0); + assert.equal(denied.events.some(x=>x.event==='worker-exited'),false); + assert.deepEqual(canonicalBytes(f.readStore().cp),bytes); + assert.equal(f.readStore().journal.length,original.journal.length); +}); + +test('M6: false Condition chooses failure Terminal without launching worker and equals Standard',async t=>{ + const f=await fixture(t,{ready:false}); + const host=await invoke(f); + assert.equal(host.code,0,host.stderr); + assert.equal(host.events.at(-1).status,'failed'); + assert.equal(host.events.some(x=>x.event==='task-published'),false); + assert.equal(f.readStore().cp.receipts.length,0); + const standard=await f.standardResult(); + assert.equal(standard.terminal,'declined'); + assert.equal(sha256Bytes(canonicalBytes(standard.state)), + sha256Bytes(canonicalBytes(f.readStore().cp.state))); +}); + +test('M6: tampered SQLite artifact byte is rejected before worker dispatch; never silently repaired',async t=>{ + const f=await fixture(t); + const killed=await invoke(f,'start','after-pending'); + assert.notEqual(killed.code,0); + const before=f.readStore(); + // External DB corruption is tested through raw SQLite only in this adversarial test. + const {DatabaseSync}=await import('node:sqlite'); + const db=new DatabaseSync(f.config.storePath); + try { + db.prepare('UPDATE fk_i_artifacts SET program_bytes = ? WHERE namespace = ? AND artifact_hash = ?') + .run(Buffer.from('tampered'),f.config.namespace,before.cp.artifactHash); + } finally {db.close()} + const attempted=await invoke(f,'resume'); + assert.notEqual(attempted.code,0); + assert.equal(attempted.events.some(x=>x.event==='worker-exited'),false); + const cpSnapshot=(()=>{ + const s=createSqliteCheckpointStore(f.config.storePath,{namespace:f.config.namespace}); + try{return s.load(f.config.runId)}finally{s.close()} + })(); + assert.deepEqual(canonicalBytes(cpSnapshot),canonicalBytes(before.cp)); +}); + +test('M6: external worker receives only public task; dedicated Linux job additionally enforces cross-UID access denial',async t=>{ + const isolation=process.env.FLOWKIT_M6_ISOLATION==='1'; + if(isolation) assert.equal(process.platform,'linux'); + const f=await fixture(t,{osIsolation:isolation}); + const run=await invoke(f); + assert.equal(run.code,0,run.stderr); + assert.equal(run.events.filter(e=>e.event==='worker-exited').length,1); + assertModelNeverSawSecret(run); + assert.equal(f.readStore().cp.status,'succeeded'); +}); + + +test('M6 S06: source/Program/Skill bytes changing while waiting fail closed without another Pending',async t=>{ + const cases=[ + {kind:'source',mutate:async f=>writeFile(f.sourcePath, + workflowText+'\n// unauthorized source edit\n')}, + {kind:'Program artifact',mutate:async f=>writeFile(f.config.programPath, + Buffer.concat([await readFile(f.config.programPath),Buffer.from('\n// unauthorized artifact edit')]))}, + {kind:'Skill instruction',mutate:async f=>writeFile(f.config.skillPath, + 'Modified Skill instruction after Agent Pending was published')}, + ]; + for(const scenario of cases) { + const f=await fixture(t); + const crashed=await invoke(f,'start','after-pending'); + assert.notEqual(crashed.code,0,scenario.kind); + const before=f.readStore(); + assert.equal(before.cp.status,'waiting'); + const original=canonicalBytes(before.cp); + await scenario.mutate(f); + const rejected=await invoke(f,'resume'); + assert.notEqual(rejected.code,0,scenario.kind); + assert.equal(rejected.events.some(e=>e.event==='worker-exited'),false,scenario.kind); + assert.deepEqual(canonicalBytes(f.readStore().cp),original,scenario.kind); + assert.equal(f.readStore().journal.length,before.journal.length,scenario.kind); + } +}); + +test('M6 S01: missing Condition input yields persistent FK_R002 error, not waiting or a model task',async t=>{ + const f=await fixture(t); + await f.updateConfig({state:{message:'hello',secret:privateSentinel}}); + const run=await invoke(f); + assert.equal(run.code,2,run.stderr); + assert.equal(run.events.at(-1)?.status,'errored'); + assert.equal(run.events.some(x=>x.event==='worker-exited'),false); + const persisted=f.readStore(); + assert.equal(persisted.cp.status,'errored'); + assert.equal(persisted.cp.terminal.causeCode,'FK_R002'); + const history=persisted.journal.length; + const resume=await invoke(f,'resume'); + assert.equal(resume.code,2); + assert.equal(f.readStore().journal.length,history); + await assert.rejects(f.standardResult(),{code:'FK_R002'}); +}); From 12344ecb3bc2ca176c1bc0a2cede4b68d360df4f Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:32:43 +0900 Subject: [PATCH 03/11] test(interactive): port M6 Host integration onto current main --- tests/integration/m6-host.mjs | 207 ++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 tests/integration/m6-host.mjs diff --git a/tests/integration/m6-host.mjs b/tests/integration/m6-host.mjs new file mode 100644 index 0000000..c1c6241 --- /dev/null +++ b/tests/integration/m6-host.mjs @@ -0,0 +1,207 @@ +/** + * M6 real Node Host process fixture. It uses the production FlowKit + * compiler artifact, SQLite Store and Ed25519 Trusted Host, then exchanges + * JSON with a distinct scripted worker process over stdin/stdout. + * + * It is intentionally not a Codex/Claude/Grok provider integration. + * No endpoint allows worker/model stdin to invoke complete() or sign receipts. + */ +import {readFile} from 'node:fs/promises'; +import {createHash} from 'node:crypto'; +import {spawn} from 'node:child_process'; +import { + createPrivateKey,createPublicKey,sign,randomUUID, +} from 'node:crypto'; +import process from 'node:process'; + +const digest=bytes=>'sha256:'+createHash('sha256').update(bytes).digest('hex'); +function drift(message){return Object.assign(new Error('FK_I_POLICY_MISMATCH: '+message),{code:'FK_I_POLICY_MISMATCH'});} +import { + createSqliteCheckpointStore,createTrustedInteractiveHost, + createAgentReceiptStatement,encodeAgentReceiptStatement, +} from '../../src/interactive/index.mjs'; + +function trace(event, fields={}) { + process.stdout.write(JSON.stringify({event,...fields})+'\n'); +} +function onlyAllowedConfig(cfg) { + if (!cfg || typeof cfg!=='object' || Array.isArray(cfg)) throw new TypeError('Expected trusted Host config'); + for(const field of ['namespace','runId','startKey','storePath','programPath','identityPath','privateKeyPath','skillPath','workerPath','skillHash']) { + if(typeof cfg[field]!=='string' || !cfg[field]) throw new TypeError('Missing Host config '+field); + } + if (!cfg.state || typeof cfg.state!=='object' || !Array.isArray(cfg.allowedInputKeys)) { + throw new TypeError('Invalid Host start configuration'); + } +} +async function spawnScriptedWorker(cfg,task) { + let command=process.execPath; + let args=[cfg.workerPath]; + if(cfg.osIsolation===true && process.platform!=='linux') { + throw new Error('Cross-UID mode requires a Linux Host'); + } + if(cfg.osIsolation===true) { + args.push(cfg.privateKeyPath,cfg.storePath); + if(process.getuid?.()===0) { + // Set in spawn options below, do not inherit Host UID or GID. + } else { + command='sudo'; + args=['-n','-u','nobody','--',process.execPath,...args]; + } + } + // Deliberately do not inherit process.env: it might contain Host secrets. + const env={ + PATH:process.env.PATH||'', + ...(process.platform==='win32'?{ + SystemRoot:process.env.SystemRoot||'C:\\Windows', + TEMP:process.env.TEMP||'',TMP:process.env.TMP||'', + }:{HOME:'/tmp'}), + }; + const options={ + cwd:process.cwd(),env,stdio:['pipe','pipe','pipe'], + ...(cfg.osIsolation===true && process.getuid?.()===0 + ? {uid:65534,gid:65534} : {}), + }; + const child=spawn(command,args,options); + return new Promise((resolve,reject)=>{ + let stdout='',stderr='',bytes=0; + const timeout=setTimeout(()=>{child.kill();reject(new Error('M6 worker timeout'))},15000); + child.stdout.on('data',chunk=>{ + bytes+=chunk.length; + if(bytes>128*1024) { + child.kill(); + reject(new Error('M6 worker output limit exceeded')); + } else stdout+=chunk.toString('utf8'); + }); + child.stderr.on('data',chunk=>{ + if(stderr.length<2048)stderr+=chunk.toString('utf8').slice(0,2048-stderr.length); + }); + child.on('error',error=>{clearTimeout(timeout);reject(error)}); + child.on('close',code=>{ + clearTimeout(timeout); + if(code!==0) { + reject(new Error('M6 external worker exited '+code+': '+stderr.slice(0,300))); + return; + } + try { + if(stdout.trim().split(/\r?\n/).length!==1) { + throw new TypeError('Worker must send one JSON response'); + } + const data=JSON.parse(stdout); + const keys=Object.keys(data).sort(); + if(keys.join(',')!=='kind,requestId,result' || data.kind!=='agent-result' + || data.requestId!==task.requestId + || !data.result || typeof data.result.answer!=='string' + || Object.keys(data.result).join(',')!=='answer') { + throw new TypeError('Uncertified worker response envelope'); + } + resolve(data); + } catch(error) {reject(error)} + }); + child.stdin.on('error',()=>{}); // close errors handled through child's close result + child.stdin.end(JSON.stringify(task)); + }); +} + +const [configPath,mode='start',cut='none']=process.argv.slice(2); +if(!configPath || !['start','resume'].includes(mode) + || !['none','after-pending','after-worker'].includes(cut)) { + throw new TypeError('Usage: m6-host.mjs start|resume none|after-pending|after-worker'); +} +const config=JSON.parse(await readFile(configPath,'utf8')); +onlyAllowedConfig(config); +const programBytes=await readFile(config.programPath); +const programIdentity=JSON.parse(await readFile(config.identityPath,'utf8')); +if(digest(programBytes)!==programIdentity.artifactHash) throw drift('Program artifact bytes changed'); +async function checkSkill(){ + if(digest(await readFile(config.skillPath))!==config.skillHash) + throw drift('Skill instruction bytes changed'); +} +await checkSkill(); +// Source is optional after compiling; when still present it MUST match pinned +// source identity. Deleting source cannot change the immutable saved Program. +if(config.sourcePath){ + let existingSource; + try {existingSource=await readFile(config.sourcePath)} + catch(e){if(e.code!=='ENOENT')throw e} + if(existingSource && digest(existingSource)!==programIdentity.sourceHash) + throw drift('Original source bytes changed'); +} +const privateKey=createPrivateKey(await readFile(config.privateKeyPath)); +const publicKey=createPublicKey(privateKey); +const store=createSqliteCheckpointStore(config.storePath,{namespace:config.namespace}); +try { + const host=createTrustedInteractiveHost({ + store, + runtime:{ + skillResolver:async()=>{await checkSkill();return readFile(config.skillPath,'utf8')}, + validateResult:(_schema,result)=>typeof result?.answer==='string'?[]:['Expected a string answer'], + }, + policy:{testHostId:'m6-isolated-node-host-v1',skillManifestHash:config.skillHash}, + trustedPublicKeys:{'m6-host-key':publicKey}, + trustedArtifacts:[programIdentity.artifactHash], + allowedInputKeys:config.allowedInputKeys, + disclosurePolicyId:'m6-explicit-input-allowlist', + authorizeDisclosure:async({task})=>( + Object.keys(task.input).every(key=>config.allowedInputKeys.includes(key)) + && task.missing.every(key=>config.allowedInputKeys.includes(key)) + ), + maxStagesPerCall:config.stageBudget??128, + }); + let frame=mode==='start' + ? await host.start({ + programBytes,programIdentity,startKey:config.startKey, + runId:config.runId,state:config.state, + }) + : await host.resume({runId:config.runId}); + trace('host-start',{status:frame.status,runId:frame.runId,revision:frame.revision}); + let loops=0; + while(loops++<1000) { + if(frame.status==='paused') { + if(frame.pauseReason!=='stage-budget') throw new Error('Unexpected durable pause'); + frame=await host.resume({runId:config.runId}); + continue; + } + if(frame.status!=='waiting')break; + await checkSkill(); + const task=await host.getPublicAgentTask({runId:frame.runId}); + if(!task || task.requestId!==frame.pending?.requestId) { + throw new TypeError('Published task and Store Pending differ'); + } + trace('task-published',{runId:task.runId,requestId:task.requestId,nodeId:task.nodeId, + revision:frame.revision,attempt:task.attempt}); + if(cut==='after-pending') { + trace('fault-injected',{where:'after-pending',requestId:task.requestId}); + process.kill(process.pid,'SIGKILL'); + } + const external=await spawnScriptedWorker(config,task); + await checkSkill(); + trace('worker-exited',{requestId:external.requestId}); + if(cut==='after-worker') { + trace('fault-injected',{where:'after-worker',requestId:task.requestId}); + process.kill(process.pid,'SIGKILL'); + } + // Host constructs this envelope. The worker cannot select run identity, + // proof ID, policy, destination node or the signer. + const receipt={ + kind:'agent-result',runId:task.runId,requestId:task.requestId, + nodeId:task.nodeId,attempt:task.attempt,result:external.result, + }; + const issuedAt=Date.now(); + const statement=createAgentReceiptStatement({ + run:store.load(task.runId),receipt,keyId:'m6-host-key', + proofId:'host-process:'+randomUUID(), + issuedAt,expiresAt:issuedAt+60000, + }); + const signature=sign(null,encodeAgentReceiptStatement(statement),privateKey) + .toString('base64url'); + frame=await host.accept({receipt,attestation:{statement,signature}}); + trace('receipt-accepted',{requestId:task.requestId, + acceptedRevision:frame.acceptedRevision,duplicate:frame.duplicate}); + } + if(loops>=1000) throw new Error('M6 stage budget made no progress'); + trace('host-terminal',{status:frame.status,runId:frame.runId,revision:frame.revision}); + if(frame.status==='errored')process.exitCode=2; + else if(!['succeeded','failed'].includes(frame.status))process.exitCode=3; +} finally { + store.close(); +} From 9f7720f145abb5d97c55f49d0794b301e6fa976a Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:32:45 +0900 Subject: [PATCH 04/11] test(interactive): port M6 Host integration onto current main --- tests/integration/m6-worker.mjs | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/integration/m6-worker.mjs diff --git a/tests/integration/m6-worker.mjs b/tests/integration/m6-worker.mjs new file mode 100644 index 0000000..34b2a96 --- /dev/null +++ b/tests/integration/m6-worker.mjs @@ -0,0 +1,38 @@ +/** M6 deterministic external worker fixture — NOT an AI model provider. */ +import {readFile} from 'node:fs/promises'; +import process from 'node:process'; + +const chunks=[]; +let total=0; +for await (const chunk of process.stdin) { + total+=chunk.length; + if(total>512*1024) throw new RangeError('Public task exceeds worker limit'); + chunks.push(chunk); +} +const task=JSON.parse(Buffer.concat(chunks).toString('utf8')); +const allowed=new Set([ + 'schemaVersion','kind','id','nodeId','runId','requestId','attempt', + 'name','instruction','input','missing','resultSchema','reasons','overview', +]); +if (!task || task.schemaVersion!==1 || task.kind!=='agent' + || typeof task.runId!=='string' || typeof task.requestId!=='string' + || typeof task.nodeId!=='string' || typeof task.instruction!=='string' + || Object.keys(task).some(key=>!allowed.has(key)) + || JSON.stringify(task).includes('HOST_PRIVATE_SENTINEL') + || Object.keys(task.input).some(key=>key!=='message') + || task.missing.some(key=>key!=='message')) { + throw new Error('MODEL_VIEW_LEAK_OR_INVALID_TASK'); +} +for(const secretPath of process.argv.slice(2)) { + let denied=false; + try {await readFile(secretPath)} catch(error) { + denied=['EACCES','EPERM'].includes(error.code); + } + if(!denied) throw new Error('MODEL_CAN_READ_HOST_PRIVATE_FILE'); +} +const message=task.input.message; +if(typeof message!=='string') throw new TypeError('Public task must have string message'); +const output={requestId:task.requestId,kind:'agent-result',result:{ + answer:'external-worker-processed:'+message, +}}; +process.stdout.write(JSON.stringify(output)+'\n'); From 1e5fb4b3ca13c28ef70f8470e3e71c6887e04252 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:32:48 +0900 Subject: [PATCH 05/11] test(interactive): port M6 Host integration onto current main --- tests/integration/m6-ledger-boundary.test.mjs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/integration/m6-ledger-boundary.test.mjs diff --git a/tests/integration/m6-ledger-boundary.test.mjs b/tests/integration/m6-ledger-boundary.test.mjs new file mode 100644 index 0000000..fa742f9 --- /dev/null +++ b/tests/integration/m6-ledger-boundary.test.mjs @@ -0,0 +1,90 @@ +/** + * M6 S07 positive regression after scoped PR #8: a near-2MiB waiting + * Checkpoint is rejected before mutation, so a valid Agent result from the + * previous safely admitted Pending still reaches a terminal state. + */ +import test,{after} from 'node:test'; +import assert from 'node:assert/strict'; +import {mkdtemp,rm} from 'node:fs/promises'; +import {tmpdir} from 'node:os'; +import path from 'node:path'; +import {analyzeSource} from '../../src/compiler/analyze.mjs'; +import {lowerInteractive} from '../../src/compiler/interactive/lower.mjs'; +import {emitInteractive} from '../../src/compiler/interactive/emit.mjs'; +import {createSqliteCheckpointStore} from '../../src/interactive/sqlite-store.mjs'; +import {createInteractiveCoordinator} from '../../src/interactive/coordinator.mjs'; +import {canonicalBytes,MAX_CHECKPOINT_BYTES} from '../../src/interactive/canonical.mjs'; + +const roots=[]; +after(async()=>{for(const dir of roots)await rm(dir,{recursive:true,force:true})}); +const workflow=[ + "import {defineWorkflow,node,terminal} from '@devcodex-labs/flowkit/dsl';", + "export default defineWorkflow({id:'m6-ledger-boundary',version:'1.0.0',name:'Ledger',nodes:[", + "node('worker',{name:'Worker',agent:'writer',inputs:['message'],", + "output:{type:'object',properties:{answer:{type:'string'}}},retries:0,next:'done'}),", + "terminal('done',{name:'Done',status:'succeeded'})]});", +].join('\n'); + +test('M6 S07: near-hard-limit waiting mutation fails before CAS; valid 64-KiB receipt reaches terminal',async()=>{ + const dir=await mkdtemp(path.join(tmpdir(),'flowkit-m6-ledger-')); + roots.push(dir); + const store=createSqliteCheckpointStore(path.join(dir,'store.db'),{namespace:'m6-s07'}); + try { + const analysis=await analyzeSource(workflow,{ + filename:'m6-ledger-boundary.workflow.js',projectRoot:dir,skillCheck:false, + }); + assert.equal(analysis.diagnostics.some(d=>d.severity==='error'),false, + JSON.stringify(analysis.diagnostics)); + const lowered=lowerInteractive(analysis); + assert.ok(lowered.ir,JSON.stringify(lowered.diagnostics)); + const compiled=emitInteractive(lowered.ir); + assert.ok(compiled.code,JSON.stringify(compiled.diagnostics)); + const api=createInteractiveCoordinator({ + store, + runtime:{ + skillResolver:async()=> 'Write a valid answer', + validateResult:(_schema,result)=> + typeof result?.answer==='string'?[]:['Expected string answer'], + }, + }); + const started=await api.start({ + programBytes:Buffer.from(compiled.code), + programIdentity:compiled.programIdentity, + runId:'s07-near-limit',startKey:'s07-near-limit',state:{message:'hello'}, + }); + assert.equal(started.status,'waiting'); + const old=store.load(started.runId); + const candidate={ + ...old,revision:old.revision+1,state:{...old.state,filler:''}, + }; + const headroom=75; + const baseSize=canonicalBytes(candidate).byteLength; + candidate.state.filler='x'.repeat(MAX_CHECKPOINT_BYTES-baseSize-headroom); + const beforeBytes=canonicalBytes(old); + const beforeJournal=store.listJournal(started.runId); + assert.ok(canonicalBytes(candidate).byteLength>MAX_CHECKPOINT_BYTES-128); + await assert.rejects(async()=>store.compareAndSwap({ + runId:started.runId,expectedRevision:old.revision, + nextCheckpoint:candidate,transitionId:'m6-s07-too-big-before-publish', + journalEntry:{kind:'m6-s07-reject-large-State'}, + }),{code:'FK_I_CHECKPOINT_CAPACITY'}); + assert.deepEqual(canonicalBytes(store.load(started.runId)),beforeBytes); + assert.deepEqual(store.listJournal(started.runId),beforeJournal); + const receipt={ + runId:started.runId,requestId:started.pending.requestId, + nodeId:started.pending.nodeId,attempt:started.pending.attempt, + result:{answer:'x'.repeat(64*1024)}, + }; + const completed=await api.complete(receipt); + assert.equal(completed.status,'succeeded'); + assert.equal(completed.state.answer.length,64*1024); + assert.equal(store.getReceipt(started.runId,receipt.requestId).acceptedRevision, + completed.acceptedRevision); + assert.equal(store.listJournal(started.runId).length,completed.revision+1); + console.log('M6_S07_LEDGER_CAPACITY_FIXED '+JSON.stringify({ + schemaVersion:1,checkpointLimitBytes:MAX_CHECKPOINT_BYTES, + unsafeMutationRejected:true,unchangedOnRejection:true, + validReceiptAccepted:true,ended:completed.status, + })); + }finally{store.close()} +}); From 438df71fa5244a671e90e5fa1857dd6047b5ce13 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:32:51 +0900 Subject: [PATCH 06/11] test(interactive): include M6 integration in current-main verification --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d06238c..9d16e5b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ ], "scripts": { "build": "node scripts/build.mjs", - "test": "node --test tests/dsl.test.mjs tests/contracts.test.mjs tests/compiler/*.test.mjs tests/interactive/*.test.mjs tests/when/*.test.mjs tests/runtime/*.test.mjs tests/cli/*.test.mjs tests/integrations/*.test.mjs tests/package/*.test.mjs", + "test": "node --test tests/dsl.test.mjs tests/contracts.test.mjs tests/compiler/*.test.mjs tests/interactive/*.test.mjs tests/integration/*.test.mjs tests/when/*.test.mjs tests/runtime/*.test.mjs tests/cli/*.test.mjs tests/integrations/*.test.mjs tests/package/*.test.mjs", "test:types": "node scripts/test-types.mjs", "check:package": "node scripts/check-package.mjs", "test:package": "node scripts/test-package.mjs", From 47339e8a0d08c584571f8ae71ae67f0b78412607 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:32:53 +0900 Subject: [PATCH 07/11] test(interactive): include M6 integration in current-main verification --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc69d4a..aa3607b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,8 +81,14 @@ jobs: - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 with: node-version: ${{ matrix.node }} + - name: Install locked dependencies for M6 Host integration + run: npm ci --ignore-scripts --no-audit --no-fund - name: Prove cross-UID model worker cannot read Host key or SQLite DB run: node tests/security/host-isolation.mjs + - name: Prove M6 Host and isolated worker integration + env: + FLOWKIT_M6_ISOLATION: '1' + run: node --test tests/integration/m6-e2e.test.mjs website: runs-on: ubuntu-24.04 From 3a1c855ec895845ec301f28f10233c0eff345c96 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:34:09 +0900 Subject: [PATCH 08/11] docs(interactive): correct inline code formatting in roadmap --- ...active-full-delivery-roadmap-2026-11-11.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/interactive-full-delivery-roadmap-2026-11-11.md b/docs/interactive-full-delivery-roadmap-2026-11-11.md index 495307d..0cfd95e 100644 --- a/docs/interactive-full-delivery-roadmap-2026-11-11.md +++ b/docs/interactive-full-delivery-roadmap-2026-11-11.md @@ -1,9 +1,9 @@ # FlowKit Interactive — Full Delivery Roadmap & Closure Ledger **Baseline date:** 2026-11-11 (UTC+8) -**Repository:** \`devcodex-labs/flowkit\` -**Frozen main:** \`ea2bb10893a9be2e5de3561fc9a660a6977ae15c\` -**Planning branch:** \`fix/m6-current-main-e2e-20261111\` +**Repository:** `devcodex-labs/flowkit` +**Frozen main:** `ea2bb10893a9be2e5de3561fc9a660a6977ae15c` +**Planning branch:** `fix/m6-current-main-e2e-20261111` > This document records the approved end-to-end route from the current M6/P0-A state through P0-B, P1, P2 and production delivery. It is a roadmap and traceability ledger, not evidence that deferred features have been implemented or passed. @@ -11,12 +11,12 @@ ### 1.1 Main and CI -- Main HEAD: \`ea2bb10893a9be2e5de3561fc9a660a6977ae15c\`. +- Main HEAD: `ea2bb10893a9be2e5de3561fc9a660a6977ae15c`. - Main CI run: [42954859078](https://github.com/devcodex-labs/flowkit/actions/runs/42954859078), completed successfully. - M1–M5 implementation surfaces and S07 capacity work exist on main. Their presence is not a blanket claim that every M6/native-host gate is passed. - PR #8, #9 and #10 are merged. -- PR #7 is open Draft, head \`e1e15a70356e53401288d393aa7527c6723fe797\`, based on an older main snapshot \`c873e3edf0f1a78b13178966cdeb408391755170\`. Its M6 tests need to be revalidated against current main. -- PR #12 is open Draft, head \`c6b42a36050d885a37831cbdf0839428b2052c85\`, based on \`ea2bb10893a9be2e5de3561fc9a660a6977ae15c\`. Its scripted C13–C20 tests and documentation CI passed; this does not pass C12/S13 native-host gates. +- PR #7 is open Draft, head `e1e15a70356e53401288d393aa7527c6723fe797`, based on an older main snapshot `c873e3edf0f1a78b13178966cdeb408391755170`. Its M6 tests need to be revalidated against current main. +- PR #12 is open Draft, head `c6b42a36050d885a37831cbdf0839428b2052c85`, based on `ea2bb10893a9be2e5de3561fc9a660a6977ae15c`. Its scripted C13–C20 tests and documentation CI passed; this does not pass C12/S13 native-host gates. ### 1.2 Evidence classes @@ -57,11 +57,11 @@ Never count P evidence as M or N. Never count a CI replay of recorded model turn 1. Rebase/recreate the M6 test changes from current main, rather than relying on the stale PR #7 base. 2. Fix the temporary project fixture so the project-root contract is tested correctly: - - create a temporary \`package.json\` and verify default ancestor discovery; and - - separately verify explicit \`projectRoot\` override. -3. Include \`tests/integration/*.test.mjs\` in the normal test command. + - create a temporary `package.json` and verify default ancestor discovery; and + - separately verify explicit `projectRoot` override. +3. Include `tests/integration/*.test.mjs` in the normal test command. 4. Run the M6 cross-process tests and Linux OS-user isolation job. -5. Run build, full \`npm run verify\`, docs examples and website checks. +5. Run build, full `npm run verify`, docs examples and website checks. 6. Keep the worker explicitly labelled scripted; do not present it as Grok/Codex/Claude provider integration. 7. If a production Core defect is found, create a separate minimal Core-fix PR and rerun relevant regressions. 8. Do not merge the old PR #7 or the replacement PR until review and exact-HEAD CI pass. From df1eec669d5f46a04f40718abf16fe111db6caf3 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:39:52 +0900 Subject: [PATCH 09/11] docs(interactive): record Phase 0 and current-main M6 test gate results --- ...nteractive-full-delivery-roadmap-2026-11-11.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/interactive-full-delivery-roadmap-2026-11-11.md b/docs/interactive-full-delivery-roadmap-2026-11-11.md index 0cfd95e..b851ae6 100644 --- a/docs/interactive-full-delivery-roadmap-2026-11-11.md +++ b/docs/interactive-full-delivery-roadmap-2026-11-11.md @@ -31,8 +31,8 @@ Never count P evidence as M or N. Never count a CI replay of recorded model turn | Phase | Scope | Required exit condition | Current state | |---|---|---|---| -| 0 | Baseline freeze and traceability | Immutable baseline, full requirement/Finding/test ledger and evidence index | In progress on this branch | -| 1 | M6 current-main test compatibility | PR #7 tests run against frozen current main; no Core semantic change hidden in test repair | Pending | +| 0 | Baseline freeze and traceability | Immutable baseline, full requirement/Finding/test ledger and evidence index | **Complete** — baseline and ledger committed on this branch | +| 1 | M6 current-main test compatibility | PR #7 tests run against frozen current main; no Core semantic change hidden in test repair | **PASS** — replacement PR #13 exact-HEAD CI 7/7; four matrix cells 369/369, Linux isolation/M6 e2e 11/11 on Node 22/24; no production Core change | | 2 | Trusted Host security design | Approved threat model, OS identity, IPC, key custody, Store ownership and provenance contract | Pending | | 3 | Trusted Host security implementation | Windows and Linux isolation, access-denial tests and secure Host operations pass | Pending | | 4 | Native Grok Build JS tool bridge | Grok Build itself calls FlowKit JS tools through Pending → model completion → JS submit/resume → Terminal | Pending | @@ -256,10 +256,11 @@ A phase cannot pass by deleting a test, weakening an assertion, changing the evi ## 15. Current next actions -1. Complete Phase 0 baseline/traceability. -2. Port PR #7 M6 integration tests onto the frozen current main in a new branch. -3. Fix project-root fixture correctly and add default-discovery plus explicit-override tests. -4. Re-run exact-HEAD M6 integration, full verification and platform CI. -5. Independently review and then proceed to Phase 2 Trusted Host security design. +1. Phase 0 baseline/traceability is complete. +2. PR #7's M6 integration tests were ported onto current main in replacement PR #13. +3. The fixture now covers default nearest-package discovery and explicit `projectRoot` override. +4. Exact HEAD `3a1c855ec895845ec301f28f10233c0eff345c96` passed CI run [42996285329](https://github.com/devcodex-labs/flowkit/actions/runs/42996285329): 7/7 jobs; Ubuntu/Windows × Node 22/24 each passed 369/369 tests; Linux OS-user isolation plus M6 e2e passed 11/11 on Node 22 and Node 24. +5. Independent code review found no production Core changes. PR #13 is ready for review/merge as test infrastructure only; this does not close native C12/S13 or M6 final acceptance. +6. After PR #13 is merged, close/supersede stale PR #7 without merging its old branch, then begin Phase 2 Trusted Host security design. **No merge, main modification, release or production semantic change is authorized by this roadmap.** From 2bd4ece542ab706f36ddde6b4fe8dee86aa6c7e0 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:44:41 +0900 Subject: [PATCH 10/11] docs(interactive): map every T/S/C requirement to its closure phase --- ...active-full-delivery-roadmap-2026-11-11.md | 88 ++++++++++++++++++- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/docs/interactive-full-delivery-roadmap-2026-11-11.md b/docs/interactive-full-delivery-roadmap-2026-11-11.md index b851ae6..ab81011 100644 --- a/docs/interactive-full-delivery-roadmap-2026-11-11.md +++ b/docs/interactive-full-delivery-roadmap-2026-11-11.md @@ -225,7 +225,87 @@ Do not infer any semantics from all_succeeded or claim that cancelling a local p | N1–N13 | design review findings | closed or explicitly deferred before dependent phase | | R1/R2, R01–R22 | historical review/falsification cases | preserved as regression and review evidence | -### 14.2 Finding ownership +### 14.2 Individual T01–T34 requirement ownership + +| ID | Frozen requirement scope | Owning phase | Required closure evidence | +|---|---|---|---| +| T01 | Pure Condition → Terminal | P0-A | true/false/missing input; deterministic FK_R002 terminal; no infinite resume | +| T02 | Agent, Skill and missing inputs | P0-A | Skill resolution, declared inputs, missing-input behavior and Standard Pending parity | +| T03 | Wait, kill process, new process completes same Run | P0-A | real process restart, same Run/request identity, durable completion | +| T04 | Invalid Schema and missing/failed validator | P0-A | invalid output rejected, retry/terminal semantics, validator contract failures | +| T05 | Old requestId, wrong node, attempt or runId | P0-A | stale/forged identity rejected before mutation; namespace boundary also closes N5 | +| T06 | Duplicate receipt idempotency and conflict | P0-A | same receipt returns original Ack; conflicting content rejected; cross-process CAS evidence | +| T07 | Source, Program, compiler and policy drift | P0-A + production upgrade | drift fails closed; immutable artifact retained; SDK upgrade/resume policy closes N6 | +| T08 | Invalid JSON, prototype keys and capacity limits | P0-A | canonicalization negatives, 2 MiB hard limit, active reserve and no-mutation rejection | +| T09 | Two-process CAS | P0-A | actual independent processes contend; only one transition commits | +| T10 | Unsupported nodes/configuration preflight | P0-A | unsupported feature/nodes rejected before execution; Program ABI and featureStage rules close N3/N9 | +| T11 | Host has no JavaScript executor | Host | explicit NO_JS_EXECUTOR/BLOCKED; real native tool loop belongs to S13 | +| T12 | Standard regression, AOT, TypeScript, SDK and error codes | All / release | Standard regressions, exports, types, package and error-code contracts | +| T13 | retries:2 means at most three attempts | P0-A | attempt count, new request identity and no extra dispatch | +| T14 | Retry exhaustion preserves FK_R004 | P0-A | terminal errored state, original cause and no invalid State commit | +| T15 | Three-stage verification and candidate state | P0-B | checkStep/verify views, candidate merge and restart behavior; N2 | +| T16 | Action Patch and writes allowlist | P0-B | allowed writes applied; unauthorized writes rejected; deletion semantics documented | +| T17 | Action succeeds but receipt is lost | P0-B | indeterminate/reconcile with stable operationId; no second execute side effect | +| T18 | Action → Verification and onFailure path | P0-B | contextError remains visible to trusted executor and terminal result; N2/N7 | +| T19 | Verification true/false/invalid | P0-B | all outcomes, candidate State and invalid verifier behavior | +| T20 | Human reject → revise → approve and prefix | P0-B | model cannot forge human decision; only independently verified human channel accepted; N4 | +| T21 | Loop 1/3 iterations and final failure | P0-B | iteration count, max-exceeded route and restart parity | +| T22 | Skill pinning and drift | P0-B + production upgrade | exact Skill identity, drift rejection and durable resume policy | +| T23 | Cancel, late result and control signals | P0-B | cancellation/terminal race, idempotent cancel and stale receipt rejection | +| T24 | Parallel all_succeeded / multiple Pending | P1 | separately approved fail-fast contract, branch state, concurrency and recovery | +| T25 | Parallel any | P2 | winner election, terminal CAS, loser cancellation and late results | +| T26 | Out-of-order/duplicate results and write conflicts | P1 | branch ordering, merge policy, duplicate handling and concurrent conflict recovery | +| T27 | Real host closure | Host / M6 | native JS tool log: Pending → model completion → JS submit/resume → Terminal; S13 | +| T28 | Source/artifact/package/types/docs consistency | All / release | reproducible compiler artifacts, package/tarball, types, CLI and executable docs | +| T29 | Kill before/after every Agent phase | P0-A, P0-B, P1 and P2 | process-kill matrix with same Run, exact Pending/operation identity and durable state | +| T30 | Forged Cursor, receipt or identity | All | tamper/replay/wrong-tenant/wrong-Program negative cases; no unauthorized mutation | +| T31 | Non-pure validator and double complete | P0-A/P0-B | pure-local contract enforced; concurrent complete has one durable outcome | +| T32 | External target without idempotency | P0-B/P1/P2 | indeterminate/reconcile policy; no false exactly-once or rollback claim | +| T33 | Standard vs Interactive comparison | All milestones | row-by-row semantic parity/difference register; no undocumented divergence | +| T34 | Long-running workflow, budgets and privacy | P0-A through production | bounded Checkpoint/Journal, recovery, disclosure minimization, capacity exit and stress evidence | + +### 14.3 Individual S01–S14 acceptance ownership + +| ID | Frozen acceptance scope | Owning phase | Closure evidence | +|---|---|---|---| +| S01 | Pure Condition → Terminal | P0-A | correct terminal, missing input becomes persistent errored state, repeated resume is stable | +| S02 | Single Agent, retries and process-cut matrix | P0-A | publish/accept/commit cut points; request identity and Pending match Store | +| S03 | Repeated invalid output and validator contract | P0-A | retries+1, FK_R004 and deterministic validator failure exit | +| S04 | Two processes complete same request | P0-A | same receipt idempotent; different receipt conflict; uncertain CAS reconciled | +| S05 | Identity and transition rejection paths | P0-A | wrong namespace/run/request/attempt/block and corrupt Store rejected without mutation | +| S06 | Source/Program/Skill/Policy drift | P0-A | pinned bytes and policy mismatch fail closed before dispatch | +| S07 | JSON adversarial inputs, Checkpoint capacity and stage budget | P0-A | canonical JSON, hard/active limits, byte-equal terminal State for budget 1/128 | +| S08 | External checkStep/verify and candidate views | P0-B | Standard-equivalent trusted executorContext and restart at each boundary | +| S09 | External Action, writes and reconcile | P0-B | Patch allowlist, receipt loss, stable operationId and no duplicate execute | +| S10 | Action → Verification → onFailure and contextError | P0-B | verifier/notify context and terminal contextError match frozen semantics | +| S11 | Human reject/revise/approve and Loop limit | P0-B | forged human submission rejected; trusted human provenance; exact loop count | +| S12 | Cancel and late receipt | P0-B | CAS winner, repeated cancel idempotency, stale receipt rejection | +| S13 | Native real-host JS tool loop | Phase 4 / M6 | raw native tool logs and independently verified Host/session/process provenance | +| S14 | Four CI cells, package, types, SDK and Standard regression | Every release gate | exact-HEAD verify, exports/types/tarball/docs and supported OS/Node matrix | + +### 14.4 C01–C24 complex acceptance ownership + +The frozen detailed matrices remain the source of exact test steps and assertions: +- [C01–C12 frozen matrix and evidence rules](https://github.com/devcodex-labs/flowkit/blob/test/interactive-complex-c01-c12-acceptance/docs/interactive-complex-acceptance-c01-c12.md) +- [C13–C24 extension and evidence tiers](https://github.com/devcodex-labs/flowkit/blob/test/interactive-complex-c01-c12-acceptance/docs/interactive-complex-acceptance-extension-c13-c24.md) +- [C13–C24 results report](https://github.com/devcodex-labs/flowkit/blob/test/interactive-complex-c01-c12-acceptance/docs/interactive-complex-acceptance-c13-c24-results-2026-11-11.md) + +| IDs | Coverage | Evidence tier / owner | +|---|---|---| +| C01–C04 | multi-Agent flow, conditions, Schema repair and retry exhaustion | P + M; Phase 5 | +| C05–C06 | process crash/recovery, receipt race and independent model sessions | P + M/N; Phase 5 | +| C07–C09 | Program/Skill/Policy drift, security and output/capacity | P + N; Phase 3/5 | +| C10–C11 | long chain and stage-budget parity | P; Phase 5 | +| C12 | native Grok/Codex host and JS tool loop | N; Phase 4/5 | +| C13–C20 | branch-budget product, forged receipt, Ack replay, trusted failure, policy drift, multi-Run, 36-Agent fail-fast, conflicting signed submissions | P; Phase 5 regression | +| C21 | cross-OS/Node DB migration | P enhancement; production compatibility, not silently upgraded to original P0-A hard gate | +| C22 | prompt injection and Host privilege attacks | P + M/N; Phase 3/5 | +| C23 | combined real-model failure/crash/concurrency scenario | M/N; Phase 5 | +| C24 | release compatibility, package and native-host evidence | P + N; Phase 6/10 | + +All C rows remain individually classified PASS/FAIL/PARTIAL/BLOCKED/DEFERRED in the detailed matrix. This summary table does not replace their individual raw evidence. + +### 14.5 Finding ownership | Finding | Planned closure | |---|---| @@ -245,7 +325,7 @@ Do not infer any semantics from all_succeeded or claim that cancelling a local p This assignment is a tracking map, not an assertion that the finding is already fixed or that a new API is approved. Any requirement-stage change requires the user's approval. -### 14.3 Global release rule +### 14.6 Global release rule No stage is complete while any applicable requirement, acceptance case, historical Finding or regression remains unclassified. Every unresolved item must be either: - fixed and re-tested; @@ -259,8 +339,8 @@ A phase cannot pass by deleting a test, weakening an assertion, changing the evi 1. Phase 0 baseline/traceability is complete. 2. PR #7's M6 integration tests were ported onto current main in replacement PR #13. 3. The fixture now covers default nearest-package discovery and explicit `projectRoot` override. -4. Exact HEAD `3a1c855ec895845ec301f28f10233c0eff345c96` passed CI run [42996285329](https://github.com/devcodex-labs/flowkit/actions/runs/42996285329): 7/7 jobs; Ubuntu/Windows × Node 22/24 each passed 369/369 tests; Linux OS-user isolation plus M6 e2e passed 11/11 on Node 22 and Node 24. +4. Exact HEAD `df1eec669d5f46a04f40718abf16fe111db6caf3` passed CI run [42997084061](https://github.com/devcodex-labs/flowkit/actions/runs/42997084061): 7/7 jobs; Ubuntu/Windows × Node 22/24 each passed 369/369 tests; Linux OS-user isolation plus M6 e2e passed 11/11 on Node 22 and Node 24. 5. Independent code review found no production Core changes. PR #13 is ready for review/merge as test infrastructure only; this does not close native C12/S13 or M6 final acceptance. 6. After PR #13 is merged, close/supersede stale PR #7 without merging its old branch, then begin Phase 2 Trusted Host security design. -**No merge, main modification, release or production semantic change is authorized by this roadmap.** +**No direct main modification or release is authorized. Merge only through a reviewed PR after exact-HEAD CI. This test/roadmap PR does not authorize production semantic changes or declare M6 complete.** From 14a62cb96fc29b121756961f7fa42baddf8cc070 Mon Sep 17 00:00:00 2001 From: rocky Date: Wed, 11 Nov 2026 18:45:15 +0900 Subject: [PATCH 11/11] docs(interactive): enumerate all C01-C24 complex acceptance scenarios --- ...active-full-delivery-roadmap-2026-11-11.md | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/interactive-full-delivery-roadmap-2026-11-11.md b/docs/interactive-full-delivery-roadmap-2026-11-11.md index ab81011..e2630de 100644 --- a/docs/interactive-full-delivery-roadmap-2026-11-11.md +++ b/docs/interactive-full-delivery-roadmap-2026-11-11.md @@ -290,18 +290,32 @@ The frozen detailed matrices remain the source of exact test steps and assertion - [C13–C24 extension and evidence tiers](https://github.com/devcodex-labs/flowkit/blob/test/interactive-complex-c01-c12-acceptance/docs/interactive-complex-acceptance-extension-c13-c24.md) - [C13–C24 results report](https://github.com/devcodex-labs/flowkit/blob/test/interactive-complex-c01-c12-acceptance/docs/interactive-complex-acceptance-c13-c24-results-2026-11-11.md) -| IDs | Coverage | Evidence tier / owner | +| ID | Frozen scenario | Evidence tier / owner | |---|---|---| -| C01–C04 | multi-Agent flow, conditions, Schema repair and retry exhaustion | P + M; Phase 5 | -| C05–C06 | process crash/recovery, receipt race and independent model sessions | P + M/N; Phase 5 | -| C07–C09 | Program/Skill/Policy drift, security and output/capacity | P + N; Phase 3/5 | -| C10–C11 | long chain and stage-budget parity | P; Phase 5 | -| C12 | native Grok/Codex host and JS tool loop | N; Phase 4/5 | -| C13–C20 | branch-budget product, forged receipt, Ack replay, trusted failure, policy drift, multi-Run, 36-Agent fail-fast, conflicting signed submissions | P; Phase 5 regression | -| C21 | cross-OS/Node DB migration | P enhancement; production compatibility, not silently upgraded to original P0-A hard gate | -| C22 | prompt injection and Host privilege attacks | P + M/N; Phase 3/5 | -| C23 | combined real-model failure/crash/concurrency scenario | M/N; Phase 5 | -| C24 | release compatibility, package and native-host evidence | P + N; Phase 6/10 | +| C01 | Four serial Agents and two Conditions | P + M; Phase 5 | +| C02 | Dynamic routes, true/false and missing-condition errors | P + M; Phase 5 | +| C03 | Schema rejection, retry and valid repair | P + M; Phase 5 | +| C04 | Invalid output and retry exhaustion | P + M; Phase 5 | +| C05 | Host crash at Pending/accept/validate/commit boundaries | P + M/N; Phase 5 | +| C06 | Two-process CAS, lost response and duplicate/conflicting receipts | P + M/N; Phase 5 | +| C07 | Source/Program/Skill/Policy drift | P + N; Phase 3/5 | +| C08 | Permissions, signature verification and data leakage | P + N; Phase 3/5 | +| C09 | Checkpoint capacity, oversize and bounded receipt behavior | P + M/N; Phase 5 | +| C10 | Long Agent chain and durable receipt ledger | P; Phase 5 | +| C11 | Stage-budget 1/128 terminal State byte parity | P; Phase 5 | +| C12 | Native Grok/Codex host and JS tool loop | N; Phase 4/5 | +| C13 | Branch truth table × stage-budget cross product | P; Phase 5 | +| C14 | Forged, stale, tampered and oversized receipts during a live Pending | P; Phase 5 | +| C15 | Historical Ack replay after Host rebuild and Terminal | P; Phase 5 | +| C16 | Trusted provider-call failure and bounded retry | P + M/N; Phase 5 | +| C17 | Host policy/binding drift mid-chain | P; Phase 5 | +| C18 | Interleaved Runs, idempotent start and conflicting start intent | P; Phase 5 | +| C19 | 36-Agent chain failing at Agent 18 with fail-fast | P; Phase 5 | +| C20 | Two different valid signed results competing for one CAS | P; Phase 5 | +| C21 | SIGKILL and same physical DB recovery across Node/OS versions | P enhancement; Phase 10 unless approved as a hard gate | +| C22 | Prompt injection and attempts to claim Host authority | P + M/N; Phase 3/5 | +| C23 | Combined live-model failure, crash/recovery and competing submitters | M/N; Phase 5 | +| C24 | Compatibility, package, docs and native-host release gate | P + N; Phase 6/10 | All C rows remain individually classified PASS/FAIL/PARTIAL/BLOCKED/DEFERRED in the detailed matrix. This summary table does not replace their individual raw evidence.