@cassiomc1/forgeloop 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ coverage.
14
14
  | Required artifact freshness | `readRequiredArtifactFingerprints`, `classifyLoadedWorkState` | `tests/checkpoint-freshness.test.js`, `tests/validate-protocol-cli.test.js` | matching hash, missing, and changed artifact cases |
15
15
  | Schema health | `inspectSchemaHealth` | `tests/schema-health.test.js` | missing, invalid, and unsupported-version schemas |
16
16
  | Evidence vocabulary | `src/core/evidence.js` | `tests/evidence.test.js` | unknown kind and incomplete record |
17
+ | Verification execution isolation | `src/core/verification-execution.js`, `src/core/runtime-context.js` | adapter call binding, disposable cwd, timeout/termination/truncation preservation, and canonical isolation combinations in `tests/verification-execution-isolation.test.js` | missing adapter under required policy, malformed/contradictory isolation metadata, and live-root cwd claims in `tests/verification-execution-isolation.test.js` |
17
18
  | Receipt semantics | `src/core/receipt.js` | `tests/receipt-semantics.test.js` | unsupported publication, review, check, and completion claims |
18
19
  | Cross-artifact conformance | `src/core/conformance.js`, `src/commands/validate-protocol.js` | `tests/conformance.test.js`, `tests/validate-protocol-cli.test.js` | `stateClassification`, derived stale details, precedence, mismatch, incomplete, and incompatible fixtures |
19
20
  | Delegation conflicts | `src/core/delegation.js` | `tests/delegation-set.test.js` | WRITE/WRITE, WRITE/READ, unknown dependency, and cycle cases |
package/DOCS_INDEX.md CHANGED
@@ -26,7 +26,8 @@ integration and guide context. Use this map before editing documentation.
26
26
  | Universal integration API | [`docs/UNIVERSAL_INTEGRATION.md`](./docs/UNIVERSAL_INTEGRATION.md) | Programmatic integration subpath, envelope semantics, and consumer map |
27
27
  | Local-first MCP adapter | [`docs/MCP.md`](./docs/MCP.md) | stdio default, optional strict loopback HTTP; server modes/capabilities and canonical resources |
28
28
  | Documentation guide | [`docs/DOCUMENTATION_GUIDE.md`](./docs/DOCUMENTATION_GUIDE.md) | Rules and checklist for modifying documentation |
29
- | ForgeLoop 1.5/MCP release checklist | [`docs/RELEASE_CHECKLIST_1_5_MCP.md`](./docs/RELEASE_CHECKLIST_1_5_MCP.md) | Integration API v1, MCP package, and publication gates |
29
+ | ForgeLoop 1.6.1 release checklist (current) | [`docs/RELEASE_CHECKLIST_1_6_1.md`](./docs/RELEASE_CHECKLIST_1_6_1.md) | Verification adapter boundary, isolation invariants, and publication gates |
30
+ | ForgeLoop 1.5/MCP release checklist (historical) | [`docs/RELEASE_CHECKLIST_1_5_MCP.md`](./docs/RELEASE_CHECKLIST_1_5_MCP.md) | Integration API v1, MCP package, and publication gates |
30
31
  | ForgeLoop 1.4 release checklist | [`docs/RELEASE_CHECKLIST_1_4.md`](./docs/RELEASE_CHECKLIST_1_4.md) | Claim-recovery, compatibility, package, and publication gates |
31
32
  | Lifecycle, gates, planning, verification, and recovery | [`LOOP_ENGINEERING.md`](./LOOP_ENGINEERING.md) | Normative process for agents and developer workflows |
32
33
  | Capability levels, discovery, and degradation | [`PROTOCOL_INTEGRATION.md`](./PROTOCOL_INTEGRATION.md) | Vendor-neutral harness contract |
@@ -39,6 +40,7 @@ integration and guide context. Use this map before editing documentation.
39
40
  | Guide content | [`ENG/`](./ENG/) | Context-specific, English-only operational guides |
40
41
  | Diagram governance | [`docs/diagrams/manifest.json`](./docs/diagrams/manifest.json) | Authoritative taxonomy, renderer mapping, canonical purposes, artifact ownership, and references |
41
42
  | Diagram maintainer entrypoint | [`docs/diagrams/README.md`](./docs/diagrams/README.md) | Typed Archify source, animated HTML explorer, animated SVG fallback, review, and regeneration workflow |
43
+ | Real Execution Proof of Concept (PoC) | [`poc/README.md`](./poc/README.md) | Non-normative, reproducible public engineering workload, audit evidence, and technical audit. Normative behavior remains owned by [`LOOP_ENGINEERING.md`](./LOOP_ENGINEERING.md). |
42
44
 
43
45
  ## Audience map
44
46
 
@@ -46,6 +48,7 @@ integration and guide context. Use this map before editing documentation.
46
48
  | --- | --- |
47
49
  | **First-time user or developer** | [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md) |
48
50
  | **AI coding agent / harness** | [`AGENTS.md`](./AGENTS.md) → [`LOOP_ENGINEERING.md`](./LOOP_ENGINEERING.md) |
51
+ | **Technical auditor / Evaluator** | [`poc/README.md`](./poc/README.md) → [`poc/reports/poc-20260826-real-execution-technical-audit-v2.md`](./poc/reports/poc-20260826-real-execution-technical-audit-v2.md) |
49
52
  | **Harness integrator** | [`PROTOCOL_INTEGRATION.md`](./PROTOCOL_INTEGRATION.md) |
50
53
  | **External runtime / orchestrator integrator** | [`ORCHESTRATOR_INTEGRATION.md`](./ORCHESTRATOR_INTEGRATION.md) |
51
54
  | **Resuming another tool / session** | [`docs/CROSS_HARNESS_CONTINUITY.md`](./docs/CROSS_HARNESS_CONTINUITY.md) |
@@ -54,7 +57,8 @@ integration and guide context. Use this map before editing documentation.
54
57
  | **Fixing a broken or stale state** | [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md) |
55
58
  | **Looking for quick recipes** | [`docs/RECIPES.md`](./docs/RECIPES.md) |
56
59
  | **Documentation contributor** | [`docs/DOCUMENTATION_GUIDE.md`](./docs/DOCUMENTATION_GUIDE.md) |
57
- | **Release maintainer (current)** | [`docs/RELEASE_CHECKLIST_1_5_MCP.md`](./docs/RELEASE_CHECKLIST_1_5_MCP.md) |
60
+ | **Release maintainer (current)** | [`docs/RELEASE_CHECKLIST_1_6_1.md`](./docs/RELEASE_CHECKLIST_1_6_1.md) |
61
+ | **Release maintainer (historical 1.5/MCP)** | [`docs/RELEASE_CHECKLIST_1_5_MCP.md`](./docs/RELEASE_CHECKLIST_1_5_MCP.md) |
58
62
  | **Release maintainer (historical 1.4)** | [`docs/RELEASE_CHECKLIST_1_4.md`](./docs/RELEASE_CHECKLIST_1_4.md) |
59
63
  | **Protocol architect / maintainer** | [`LOOP_SYSTEM_DESIGN.md`](./LOOP_SYSTEM_DESIGN.md) + [`schemas/`](./schemas/) |
60
64
  | **Security auditor** | [`THREAT_MODEL.md`](./THREAT_MODEL.md) |
@@ -63,6 +67,7 @@ integration and guide context. Use this map before editing documentation.
63
67
  ## Task map
64
68
 
65
69
  - **Start my first task**: [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md)
70
+ - **Inspect real execution PoC and audit evidence**: [`poc/README.md`](./poc/README.md)
66
71
  - **Resume after switching tools**: [`docs/CROSS_HARNESS_CONTINUITY.md`](./docs/CROSS_HARNESS_CONTINUITY.md)
67
72
  - **Check CLI options and syntax**: [`docs/CLI_REFERENCE.md`](./docs/CLI_REFERENCE.md)
68
73
  - **Understand what `.forgeloop/` stores**: [`docs/ARTIFACT_REFERENCE.md`](./docs/ARTIFACT_REFERENCE.md)
@@ -149,7 +149,7 @@ Every verification command path is classified by resolution mode:
149
149
 
150
150
  **Validator-enforced rule**: Any verification command executed via an installation-capable or explicit-installation resolution mode without a valid canonical installation authority grant is rejected by `record-check`, `audit`, and `complete` with error code `E_INSTALLATION_AUTHORITY_REQUIRED`, `E_AUTHORITY_INVALID`, `E_AUTHORITY_SCOPE_MISMATCH`, or `E_AUTHORITY_UNTRUSTED_SOURCE` and cannot contribute to `VALID` completion.
151
151
 
152
- Recognized command dispatchers (such as `npm test`, `npm start`, `npm stop`, `npm restart`, `npm run <script>`, `npm run-script <script>`, `npm rum <script>`, `npm urn <script>`) are classified by their effective package resolution behavior across recognized lifecycle scripts before process launch. npm invocation parsing recognizes options (e.g. `--silent`, `--loglevel=error`) before the subcommand. Recognized npm-script dispatch is resolved recursively before process launch. `npm restart` uses npm's restart-specific lifecycle semantics (`prerestart`, `prestop`, `stop`, `poststop`, `prestart`, `start`, `poststart`, `postrestart` when `restart` is absent; `prerestart`, `restart`, `postrestart` when `restart` is present) rather than generic pre/main/post handling. ForgeLoop fails closed (`mayInstall: true`) when recursive npm-script resolution encounters a cycle or exceeds its maximum resolution depth (16). ForgeLoop does not resolve npm workspace selection in `run-check` for `0.1.15`. npm script executions using `--workspace`, `-w`, `--workspaces`, or `--ws` fail closed (`E_COMMAND_RESOLUTION_AMBIGUOUS`) because the effective `package.json` execution context may differ from the current ForgeLoop target. Run ForgeLoop against the selected workspace directory directly instead. If any nested lifecycle script invokes an installation-capable command (such as `npx`, `npm exec`, or `pnpm dlx`), the execution is elevated to `INSTALL_CAPABLE_RESOLUTION` and blocked before launch without authority.
152
+ Recognized command dispatchers (such as `npm test`, `npm start`, `npm stop`, `npm restart`, `npm run <script>`, `npm run-script <script>`, `npm rum <script>`, `npm urn <script>`) are classified by their effective package resolution behavior across recognized lifecycle scripts before process launch. npm invocation parsing recognizes options (e.g. `--silent`, `--loglevel=error`) before the subcommand. Recognized npm-script dispatch is resolved recursively before process launch. `npm restart` uses npm's restart-specific lifecycle semantics (`prerestart`, `prestop`, `stop`, `poststop`, `prestart`, `start`, `poststart`, `postrestart` when `restart` is absent; `prerestart`, `restart`, `postrestart` when `restart` is present) rather than generic pre/main/post handling. ForgeLoop fails closed (`mayInstall: true`) when recursive npm-script resolution encounters a cycle or exceeds its maximum resolution depth (16). ForgeLoop does not resolve npm workspace selection in `run-check`. npm script executions using `--workspace`, `-w`, `--workspaces`, or `--ws` fail closed (`E_COMMAND_RESOLUTION_AMBIGUOUS`) because the effective `package.json` execution context may differ from the current ForgeLoop target. Run ForgeLoop against the selected workspace directory directly instead. If any nested lifecycle script invokes an installation-capable command (such as `npx`, `npm exec`, or `pnpm dlx`), the execution is elevated to `INSTALL_CAPABLE_RESOLUTION` and blocked before launch without authority.
153
153
 
154
154
  **npm Classification Model**: npm classification is semantic and fail-closed. Unknown npm commands are not assumed safe. The classifier specifically identifies install-capable families including: `exec`/`x`, `install` aliases, `ci` aliases, `install-test` families, `install-ci-test` families, `update` aliases, `audit fix`, and conditional `init`/`create`/`innit` invocations. Unknown or ambiguous semantics fail closed (`E_COMMAND_RESOLUTION_AMBIGUOUS`).
155
155
 
@@ -162,6 +162,27 @@ resolution is rejected without a valid host-attested authority, while
162
162
  `npx --no-install` remains a non-installing path and may fail honestly when a
163
163
  tool is absent. `run-check` launches the supplied argv without a shell.
164
164
 
165
+ ### Verification execution isolation
166
+
167
+ Without a trusted adapter, verification executes in the live project and is
168
+ recorded as `NATIVE_PROJECT` (`isolated: false`, `liveProjectWritable: true`,
169
+ inherited network and environment). A host may supply a trusted verification
170
+ execution adapter and an isolation policy through the integration runtime
171
+ context (`verificationExecutionAdapter`, `verificationExecutionPolicy`).
172
+ The policy modes are `NONE`, `NATIVE_PROJECT`, `PROJECT_ISOLATED`
173
+ (`isolated: true`, `liveProjectWritable: false`), and `SYSTEM_ISOLATED`
174
+ (additionally `networkPolicy: DENIED`). `liveProjectWritable` is an enforced
175
+ host guarantee reported by the adapter, not an inference from a different
176
+ working directory, and a disposable copy alone does not satisfy filesystem
177
+ isolation. Isolated execution must use a working directory separate from the
178
+ protocol project root. Contradictory isolation metadata is intrinsically
179
+ rejected before evidence persistence with `E_VERIFICATION_EXECUTION_INVALID`,
180
+ and verification that cannot satisfy the required isolation boundary fails
181
+ closed with `E_VERIFICATION_ISOLATION_UNAVAILABLE` instead of running in the
182
+ live project. ForgeLoop owns the adapter contract and evidence semantics; the
183
+ harness owns the concrete isolation backend and no specific backend is
184
+ normative.
185
+
165
186
  `forgeloop record-check` is serialization-only. Its `--command` value is
166
187
  metadata and is never executed. A `kind: command`, `evidenceKind: OBSERVED`
167
188
  check must carry `provenance: FORGELOOP_EXECUTED` and a valid `executionRef`;
@@ -204,6 +204,50 @@ metadata. Invalid or missing references return `E_EXECUTION_REF_INVALID`; an
204
204
  observed command without ForgeLoop provenance returns
205
205
  `E_COMMAND_PROVENANCE_UNATTESTED`.
206
206
 
207
+ ### Verification execution adapter boundary
208
+
209
+ Verification execution is separated from protocol state through a trusted
210
+ adapter boundary. A host supplies both through the integration runtime
211
+ context, and neither is accepted as CLI flags, input, or project files:
212
+
213
+ ```js
214
+ createForgeLoopContext({
215
+ verificationExecutionAdapter: { execute: async (request) => { /* ... */ } },
216
+ verificationExecutionPolicy: { requiredIsolation: "PROJECT_ISOLATED" },
217
+ });
218
+ ```
219
+
220
+ The adapter receives a frozen request (`argv`, `protocolProjectRoot`, `taskId`,
221
+ `checkId`, `requirement`, `timeoutMs`, `resolution`) and returns the execution
222
+ result plus isolation metadata. Isolation modes are:
223
+
224
+ | Mode | `isolated` | `liveProjectWritable` | Network/environment |
225
+ | --- | --- | --- | --- |
226
+ | `NATIVE_PROJECT` | `false` | `true` | Inherited |
227
+ | `PROJECT_ISOLATED` | `true` | `false` | Adapter-declared policy |
228
+ | `SYSTEM_ISOLATED` | `true` | `false` | `networkPolicy: DENIED` required |
229
+
230
+ ForgeLoop owns the adapter contract and evidence semantics; the harness owns
231
+ the concrete isolation backend, and no specific backend is normative in
232
+ ForgeLoop core documentation. The boundary is fail-closed:
233
+
234
+ - isolation metadata must be internally consistent with its declared mode
235
+ (`NATIVE_PROJECT` is never isolated; isolated modes are never
236
+ `liveProjectWritable`; `SYSTEM_ISOLATED` never inherits network access).
237
+ Contradictory metadata is rejected with `E_VERIFICATION_EXECUTION_INVALID`
238
+ before evidence persistence;
239
+ - isolated execution must use a working directory separate from the protocol
240
+ project root;
241
+ - verification that cannot satisfy the required isolation boundary fails with
242
+ `E_VERIFICATION_ISOLATION_UNAVAILABLE` and must never fall back to running
243
+ in the live project.
244
+
245
+ `protocol-info --json` advertises this capability as
246
+ `features.verificationExecutionIsolation` (version 1), including the supported
247
+ modes and the `protocolProjectRootSeparateFromExecutionCwd` invariant. The
248
+ modes, public error codes, and `createForgeLoopContext` are exported from
249
+ `@cassiomc1/forgeloop/integration`.
250
+
207
251
  ## Missing tool capability
208
252
 
209
253
  A missing tool is a capability gap, not installation authority.
package/README.md CHANGED
@@ -28,6 +28,7 @@ relevant guides.
28
28
  ## Where should I start?
29
29
 
30
30
  - **New to ForgeLoop** → [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md)
31
+ - **Inspect a real ForgeLoop execution** → [`poc/README.md`](./poc/README.md)
31
32
  - **Full protocol specification** → [`LOOP_ENGINEERING.md`](./LOOP_ENGINEERING.md)
32
33
  - **Integrating an AI harness** → [`PROTOCOL_INTEGRATION.md`](./PROTOCOL_INTEGRATION.md)
33
34
  - **Continuing another harness's task** → [`docs/CROSS_HARNESS_CONTINUITY.md`](./docs/CROSS_HARNESS_CONTINUITY.md)
@@ -38,6 +39,21 @@ relevant guides.
38
39
  - **System architecture & safety** → [`LOOP_SYSTEM_DESIGN.md`](./LOOP_SYSTEM_DESIGN.md) & [`THREAT_MODEL.md`](./THREAT_MODEL.md)
39
40
  - **Documentation index & ownership** → [`DOCS_INDEX.md`](./DOCS_INDEX.md)
40
41
 
42
+ ## Real execution proof
43
+
44
+ ForgeLoop includes a public real-execution PoC with the workload,
45
+ protocol artifacts, trusted command provenance, execution receipt,
46
+ event history, cryptographic evidence manifest, and technical audit.
47
+
48
+ The original task reached validator-backed `COMPLETE / VALID`.
49
+ The evidence package also preserves a later
50
+ `E_RECEIPT_PATH_MISMATCH`, detected after evidence publication itself
51
+ changed the repository.
52
+
53
+ - [PoC overview](./poc/README.md)
54
+ - [Canonical technical audit](./poc/reports/poc-20260826-real-execution-technical-audit-v2.md)
55
+ - [Evidence package](./poc/evidence/poc-20260826-real-execution/)
56
+
41
57
  ## Catalog
42
58
 
43
59
  | Topic | Guide |
package/THREAT_MODEL.md CHANGED
@@ -81,6 +81,7 @@ provide a universal exactly-once guarantee.
81
81
  | Unsupported profile fact | Turns an agent decision into a durable user fact | `PROJECT_PROFILE.md` and `.forgeloop/sources.json` | Source IDs, source-kind validation, unknown-reference rejection, and explicit misclassification failures | Arbitrary Markdown semantics still require a human or host-specific parser | `tests/profile-provenance.test.js`, `src/core/profile.js` |
82
82
  | Weak verification | Treats a vague or inferred claim as observed evidence | Receipt checks and coverage | Versioned check schema, contradictory-status rejection, observed-evidence requirements, and coverage matrix | Evidence remains local and declarative; it is not a remote attestation service | `tests/evidence-coverage.test.js`, `tests/completion.test.js` |
83
83
  | Unattested observed command | An actor supplies `--command "..."` or forged command metadata and makes an unrun process appear to be observed evidence | Check provenance, execution artifact, and process boundary | `run-check` captures exact argv, cwd, resolution, timestamps, exit status, and task/check binding; it classifies before launch, uses `shell: false`, and rejects install-capable resolution without trusted authority. `record-check --command` is metadata only; command `OBSERVED` checks require `FORGELOOP_EXECUTED` plus a valid `executionRef`; completion, audit, protocol validation, and bundles revalidate it | The local execution artifact is not cryptographic remote attestation, and a separately privileged process can alter the target after execution | `tests/run-check.test.js`, `tests/completion-cli.test.js`, `tests/verification-capability.test.js`, `tests/validate-protocol-cli.test.js` |
84
+ | Contradictory execution isolation metadata | A custom execution adapter reports guarantees the host does not enforce, for example `NATIVE_PROJECT` claiming `isolated=true`, an isolated mode claiming `liveProjectWritable=true`, or `SYSTEM_ISOLATED` claiming inherited network access | Verification execution adapter boundary and execution evidence | Adapter results are normalized and validated before evidence persistence: canonical mode/guarantee invariants are intrinsically enforced, isolated execution must use a cwd separate from the protocol project root, and violations fail closed with `E_VERIFICATION_EXECUTION_INVALID` or `E_VERIFICATION_ISOLATION_UNAVAILABLE` | The isolation report is still host-supplied; ForgeLoop validates coherence and policy satisfaction, not the underlying OS enforcement | `tests/verification-execution-isolation.test.js` |
84
85
  | Malicious receipt | Turns local claims into false publication or completion claims | Execution receipt JSON | Semantic evidence checks for completion, checks, review, push, commit, and deployment; explicit publication booleans | Evidence text is declarative and must still be reviewed for provenance | `tests/receipt-semantics.test.js`, `tests/observability.test.js` |
85
86
  | Malicious task brief | Grants a child task more authority than intended | Delegation brief JSON and host harness | Relative path boundaries, guide/verification/authority checks, secret-free validation, set validation, parent integration ownership | The host harness remains responsible for OS permissions and execution policy | `tests/delegation.test.js`, `tests/delegation-set.test.js` |
86
87
  | Artifact content exposure | Sensitive material enters portable artifacts or diagnostics | State, receipt, delegation, evidence, and repository text | Nested key/value detection, shaped marker patterns, scanner coverage, no content echo in errors | Content scanners cannot prove that an unknown encoding is harmless | `tests/security-limits.test.js`, `tests/test_scan_secrets.py`, `scripts/scan_secrets.py` |
@@ -101,7 +102,7 @@ provide a universal exactly-once guarantee.
101
102
  | Environment-injected trust root | The active actor creates an authority file outside the project target and sets `FORGELOOP_AUTHORITY_FILE` or `FORGELOOP_AUTHORITY_DIR` when invoking ForgeLoop | Actor-controlled process environment versus host-attested runtime authority | Standalone CLI uses `trustMode: NONE`; environment-selected sources are metadata/candidates only and fail with `E_AUTHORITY_UNTRUSTED_SOURCE`; trusted authority requires an internal `HOST_ATTESTED` context not exposed as a self-assertable CLI option | If the host integration allows the actor to mutate or replace the attested source/context, the host trust boundary is compromised | Environment-injection rejection, standalone audit/complete rejection, host-attested positive path |
102
103
  | Stale receipt recovery dead-end | Work state changes after preparing a receipt, leaving a mismatch that cannot be refreshed because the old receipt is rejected during re-preparation | Preparation lifecycle and recovery action resolution | Recoverable stale receipt binding in `prepareCompletion`, executable `PREPARE_COMPLETION` return from `next`, atomic refresh of stateFingerprint and changedPaths | Manual file corruption outside CLI commands requires manual diagnostic recovery | `tests/stale-receipt-recovery.test.js`, `tests/next-executability.test.js` |
103
104
  | Recursive npm script dispatch | A recognized npm lifecycle script invokes another npm script, which later invokes an installation-capable resolver (e.g. `test` -> `npm run visual` -> `npx package`) | Recognized npm dispatcher semantics before ForgeLoop process launch | Recursive npm-script resolution with cycle detection, maximum depth (16), lifecycle hook inspection, restart special semantics, and fail-closed behavior when the resolver cannot prove the chain is non-installing | Opaque executables may spawn arbitrary descendants. Full descendant-process attestation requires host-level process controls and is outside this release | `tests/run-check.test.js`, `tests/verification-capability.test.js` |
104
- | npm invocation rewriting and workspace dispatch | npm configuration flags appear before the subcommand, or npm workspace selectors cause script execution to occur against a package.json different from the ForgeLoop target root | Raw npm argv versus effective npm command and execution context | Canonical npm invocation parsing, effective subcommand extraction, workspace flag detection across the full npm argv, and fail-closed workspace script handling when the selected package.json cannot be proven from the current target | ForgeLoop 0.1.15 intentionally does not implement full npm workspace resolution. Users should execute run-check from the selected workspace target directory | `tests/run-check.test.js`, `tests/verification-capability.test.js` |
105
+ | npm invocation rewriting and workspace dispatch | npm configuration flags appear before the subcommand, or npm workspace selectors cause script execution to occur against a package.json different from the ForgeLoop target root | Raw npm argv versus effective npm command and execution context | Canonical npm invocation parsing, effective subcommand extraction, workspace flag detection across the full npm argv, and fail-closed workspace script handling when the selected package.json cannot be proven from the current target | ForgeLoop intentionally does not implement full npm workspace resolution. Users should execute run-check from the selected workspace target directory | `tests/run-check.test.js`, `tests/verification-capability.test.js` |
105
106
  | Unclassified npm Install-Capable Command | The npm security classifier recognizes only a small denylist of package-mutating commands. Another official npm command or alias with install/update/bootstrap semantics falls through as a local package command | Effective npm command semantics versus ForgeLoop's command classifier | Semantic npm command classification with explicit install-capable families, explicit script-dispatch families, a deliberately small non-installing allowlist, and fail-closed behavior for unknown or ambiguous npm commands | Future npm commands are blocked until ForgeLoop explicitly classifies them | `tests/verification-capability.test.js`, `tests/run-check.test.js` |
106
107
  | Policy weakening bypass | An actor weakens complexity or security thresholds mid-task to bypass verification failures | Task policy snapshot (`policy-snapshot.json`) and semantic policy diff | `policy-diff` classifies rule relaxations as `WEAKEN`, preflight snapshots enforce baseline digests, and `complete` blocks on `E_POLICY_WEAKENING` | A project operator with local filesystem access can authoritatively update project rules | `tests/policy-autonomy.test.js`, `tests/policy-hardening.test.js` |
107
108
  | Inert check evasion | An actor relies on an inert or non-matching checker to create false verification pass | Policy engine checker inspection | Scanned file counters and adapter target checks; unproven or inert project rules trigger `E_CHECK_INERT` | Discovered rules gracefully downgrade to advisory without stopping execution | `tests/policy-autonomy.test.js`, `tests/policy-hardening.test.js` |
@@ -423,8 +423,17 @@ Attested command execution provenance artifact generated by `forgeloop run-check
423
423
  - `requirement` *(string, required, minLength: 1)*
424
424
  - `verificationCycle` *(integer, required, minimum: 1)*
425
425
  - `kind` *(string, required, const: `COMMAND_EXECUTION`)*
426
+ - `executionKind` *(string, optional, enum: `VERIFICATION`, `DURABLE_ACTION`)*
426
427
  - `argv` *(array<string>, required, minItems: 1)*
428
+ - `protocolProjectRoot` *(string, optional, minLength: 1)*
427
429
  - `cwd` *(string, required, minLength: 1)*
430
+ - `executionIsolation` *(string, optional, enum: `NATIVE_PROJECT`, `PROJECT_ISOLATED`, `SYSTEM_ISOLATED`)*
431
+ - `isolation` *(object, optional)*
432
+ - `mode` *(string, required, enum: `NATIVE_PROJECT`, `PROJECT_ISOLATED`, `SYSTEM_ISOLATED`)*
433
+ - `isolated` *(boolean, required)*
434
+ - `liveProjectWritable` *(boolean, required)*
435
+ - `networkPolicy` *(string, required, minLength: 1)*
436
+ - `environmentPolicy` *(string, required, minLength: 1)*
428
437
  - `resolution` *(object, required)*
429
438
  - `resolutionMode` *(string, required, minLength: 1)*
430
439
  - `mayInstall` *(boolean, required)*
@@ -450,6 +459,16 @@ Attested command execution provenance artifact generated by `forgeloop run-check
450
459
 
451
460
  <!-- END FORGELOOP GENERATED: schema:execution -->
452
461
 
462
+ Isolation metadata must be intrinsically truthful. `liveProjectWritable` is an
463
+ enforced host guarantee reported by the trusted execution adapter, not a claim
464
+ implied by a different working directory; a disposable copy alone is
465
+ insufficient. Canonical combinations are `NATIVE_PROJECT` (`isolated=false`,
466
+ `liveProjectWritable=true`), `PROJECT_ISOLATED` (`isolated=true`,
467
+ `liveProjectWritable=false`), and `SYSTEM_ISOLATED` (`isolated=true`,
468
+ `liveProjectWritable=false`, `networkPolicy=DENIED`). Contradictory metadata is
469
+ rejected with `E_VERIFICATION_EXECUTION_INVALID` before the execution artifact
470
+ is persisted, even when the required isolation policy would be `NONE`.
471
+
453
472
  ---
454
473
 
455
474
  ### 2.13 `task-state/<taskKey>/task.json`
package/docs/MCP.md CHANGED
@@ -105,7 +105,7 @@ forgeloop-mcp-http --project /repo --mode safe # 127.0.0.1:3333
105
105
 
106
106
  | Component | Current contract |
107
107
  | --- | --- |
108
- | ForgeLoop core package | `1.5.x` repository generation |
108
+ | ForgeLoop core package | `>=1.5.0 <2` dependency range; current repository generation `1.6.x` |
109
109
  | ForgeLoop protocol | `1` |
110
110
  | Integration API | `1` |
111
111
  | MCP package | `0.1.x` initial package |
package/docs/RECIPES.md CHANGED
@@ -394,7 +394,7 @@ After commit ambiguity is settled as `COMMITTED`, verify the independent
394
394
  postcondition before completion:
395
395
 
396
396
  ```bash
397
- forgeloop run-check --task release --id check-release-live --requirement publication -- node scripts/check-release-live.js
397
+ forgeloop run-check --task release --id check-release-live --requirement publication -- npm view your-package-name@1.0.0 version
398
398
  forgeloop action-verify --task release --action action-publish --evidence <execution-ref>
399
399
  ```
400
400
 
@@ -0,0 +1,121 @@
1
+ # ForgeLoop 1.6.1 release checklist
2
+
3
+ Preparation checklist for the `@cassiomc1/forgeloop` 1.6.1 release. It does not
4
+ authorize publication. Historical checklists:
5
+ [`RELEASE_CHECKLIST_1_5_MCP.md`](./RELEASE_CHECKLIST_1_5_MCP.md),
6
+ [`RELEASE_CHECKLIST_1_4.md`](./RELEASE_CHECKLIST_1_4.md).
7
+
8
+ ## Version and contract identity
9
+
10
+ - [ ] Core package version is `1.6.1` in `package.json` and the lockfile root.
11
+ - [ ] ForgeLoop protocol version is `1`.
12
+ - [ ] Integration API version is `1`
13
+ (`FORGELOOP_INTEGRATION_API_VERSION`).
14
+ - [ ] Candidate version is absent from npm and the `v1.6.1` tag is absent from
15
+ origin before release preparation starts.
16
+ - [ ] `CHANGELOG.md` has a versioned `1.6.1` section with the actual release
17
+ date and an empty `Unreleased` section above it.
18
+
19
+ ## Verification execution adapter contract (new in 1.6.x line)
20
+
21
+ - [ ] `src/core/verification-execution.js` defines the adapter boundary,
22
+ `VERIFICATION_ISOLATION_MODES`, and the two public error codes.
23
+ - [ ] `runtimeContext.verificationExecutionAdapter` and
24
+ `runtimeContext.verificationExecutionPolicy` are validated at context
25
+ creation and never accepted as CLI flags or command input.
26
+ - [ ] `createForgeLoopContext`, the isolation modes, and both error codes are
27
+ exported from `@cassiomc1/forgeloop/integration`.
28
+ - [ ] `protocol-info --json` advertises
29
+ `features.verificationExecutionIsolation` (version 1, adapter-backed,
30
+ modes enumerated, `protocolProjectRootSeparateFromExecutionCwd: true`).
31
+
32
+ ## Isolation metadata invariants
33
+
34
+ - [ ] `NATIVE_PROJECT` requires `isolated: false` and
35
+ `liveProjectWritable: true`; it is never described as isolated.
36
+ - [ ] `PROJECT_ISOLATED` and `SYSTEM_ISOLATED` require `isolated: true` and
37
+ `liveProjectWritable: false`; `SYSTEM_ISOLATED` additionally requires
38
+ `networkPolicy: DENIED`.
39
+ - [ ] Contradictory isolation metadata is rejected with
40
+ `E_VERIFICATION_EXECUTION_INVALID` before evidence persistence.
41
+ - [ ] Isolated execution must use a cwd separate from the protocol project
42
+ root; violation fails closed with `E_VERIFICATION_EXECUTION_INVALID`.
43
+ - [ ] Unsatisfiable isolation policy fails closed with
44
+ `E_VERIFICATION_ISOLATION_UNAVAILABLE` and never falls back to the live
45
+ project.
46
+ - [ ] Execution records persist `executionKind`, `protocolProjectRoot`,
47
+ `executionIsolation`, and the `isolation` object per
48
+ `schemas/execution.schema.json`.
49
+
50
+ ## Durable-action invariants
51
+
52
+ - [ ] Trusted `COMMITTED` reconciliation replays exactly once; reconciled
53
+ mirrors are corroborations, never second transitions.
54
+ - [ ] Action verification requires an independent passed execution covering the
55
+ action's exact immutable requirement.
56
+ - [ ] `REQUIRE_APPROVAL` authorizations bind approval fingerprints validated by
57
+ readiness and audit.
58
+ - [ ] Public provenance metadata matches behavior
59
+ (`CALLER_REPORTED` / `EXTERNAL_OBSERVED`).
60
+
61
+ ## PoC evidence validation
62
+
63
+ - [ ] `npm run poc:evidence:verify` passes against committed evidence bundles.
64
+ - [ ] `npm run poc:evidence:test` passes.
65
+ - [ ] PoC docs (`poc/README.md`,
66
+ `poc/FORGELOOP_REAL_EXECUTION_POC.md`) match committed evidence paths,
67
+ manifests, and hashes.
68
+
69
+ ## Documentation freshness
70
+
71
+ - [ ] `npm run docs:generate` leaves no diff (generated regions current).
72
+ - [ ] `npm run docs:generated:check` passes.
73
+ - [ ] `npm run docs:conformance` passes.
74
+ - [ ] `npm run docs:diagrams:check` passes (typed Archify source unchanged or
75
+ regenerated with receipt).
76
+ - [ ] `npm run docs:check` passes.
77
+ - [ ] Normative docs represent the post-`v1.6.0` verification execution
78
+ boundary without making a harness-specific backend normative.
79
+ - [ ] `DOCS_INDEX.md` points release maintainers to this checklist as current.
80
+
81
+ ## Local validation gates
82
+
83
+ - [ ] `npm run dependency:policy` passes (runtime dependencies remain zero).
84
+ - [ ] `npm run lint` passes.
85
+ - [ ] `npm test` passes.
86
+ - [ ] `npm run coverage` passes.
87
+ - [ ] `npm run pack:check` passes.
88
+ - [ ] Python validators pass: `python3 -m unittest discover -s tests`,
89
+ `validate_markdown.py` (+ self-test), `validate_loop_system.py`
90
+ (+ self-test).
91
+ - [ ] `python3 scripts/scan_secrets.py` passes.
92
+ - [ ] `npm pack` tarball inspected: correct name/version, intended docs, no
93
+ secrets, no `.git`, no `.forgeloop` execution state.
94
+
95
+ ## Protected-branch and PR gates
96
+
97
+ - [ ] Changes reach `main` only through a reviewed PR; `main-protection`
98
+ ruleset never bypassed.
99
+ - [ ] Required checks green: `audit`, `CodeQL`, `Verify generated Archify
100
+ diagram`, `validate (22)`, `tarball smoke (ubuntu-latest)`,
101
+ `dependency-review`; every other relevant check green.
102
+ - [ ] No unresolved review threads; PR merged without admin bypass.
103
+
104
+ ## Publication boundary
105
+
106
+ - [ ] Post-merge `main` workflows green before tagging.
107
+ - [ ] npm candidate version and tag re-checked for collision immediately before
108
+ tagging.
109
+ - [ ] Annotated tag `v1.6.1` created on the exact validated `main` commit and
110
+ pushed once; never moved after publication.
111
+ - [ ] `npm-publish.yml` retains `contents: read` + `id-token: write`; no
112
+ `NPM_TOKEN`/`NODE_AUTH_TOKEN` is added anywhere.
113
+ - [ ] `Publish npm package` workflow succeeds (trusted OIDC publishing).
114
+ - [ ] `Release notes` workflow succeeds; GitHub Release is not draft or
115
+ prerelease and carries the checksum asset.
116
+ - [ ] npm registry reports the exact version with `gitHead` equal to the
117
+ release commit.
118
+ - [ ] `npm run release:identity` returns `RELEASE_IDENTITY_VALID` with every
119
+ individual check `ok`.
120
+ - [ ] Optional clean-install smoke test installs `@cassiomc1/forgeloop@1.6.1`
121
+ and `forgeloop --version` reports `1.6.1`.
@@ -462,6 +462,31 @@ A verification check requires an executable or tool that is not installed in the
462
462
 
463
463
  ---
464
464
 
465
+ ### Symptom: Verification Reports Contradictory Isolation Metadata
466
+
467
+ #### Error Code: `E_VERIFICATION_EXECUTION_INVALID`
468
+
469
+ #### What it means
470
+
471
+ The trusted verification execution adapter returned isolation metadata that contradicts itself, for example `NATIVE_PROJECT` claiming `isolated=true`, an isolated mode claiming `liveProjectWritable=true`, or `SYSTEM_ISOLATED` claiming `networkPolicy=INHERITED`. The execution artifact is rejected before evidence persistence.
472
+
473
+ #### Likely causes
474
+
475
+ 1. A custom execution adapter hard-coded isolation fields instead of reporting what the host actually enforced.
476
+ 2. A disposable-copy workspace reported itself as system isolated, or claimed the live project is not writable when no filesystem boundary enforces it.
477
+
478
+ #### Safe recovery
479
+
480
+ 1. Repair the adapter so each isolation mode reports its canonical guarantees: `NATIVE_PROJECT` (`isolated=false`, `liveProjectWritable=true`), `PROJECT_ISOLATED` (`isolated=true`, `liveProjectWritable=false`), `SYSTEM_ISOLATED` (`isolated=true`, `liveProjectWritable=false`, `networkPolicy=DENIED`).
481
+ 2. `liveProjectWritable` is an enforced host guarantee, not a claim implied by a different working directory; a disposable copy alone is insufficient.
482
+ 3. Rerun verification after the adapter reports truthful metadata.
483
+
484
+ #### Do not
485
+
486
+ **Do not weaken the required isolation policy to bypass contradictory metadata, and do not persist execution evidence that claims guarantees the host does not enforce.**
487
+
488
+ ---
489
+
465
490
  ### Symptom: Installation Authority Required
466
491
 
467
492
  #### Error Code: `E_INSTALLATION_AUTHORITY_REQUIRED`
@@ -920,6 +945,8 @@ forgeloop next --task <id> --json
920
945
  | `E_TRACE_SNAPSHOT_INCONSISTENT` | Task artifacts changed while the execution trace was being read. | Rerun the read-only projection to obtain a consistent view. |
921
946
  | `E_TRAJECTORY_REFERENCE_REQUIRED` | Comparative efficiency requires a reference scenario with positive comparableSteps. | Provide --scenario with reference.comparableSteps, or omit efficiency from the result. |
922
947
  | `E_TRAJECTORY_SCENARIO_INVALID` | Trajectory scenario file is missing required fields or schema-invalid. | Correct the scenario JSON against schemas/trajectory-scenario.schema.json. |
948
+ | `E_VERIFICATION_EXECUTION_INVALID` | The trusted verification execution adapter returned incomplete or invalid execution metadata. | Repair the adapter contract and rerun verification; do not promote incomplete execution evidence. |
949
+ | `E_VERIFICATION_ISOLATION_UNAVAILABLE` | Verification cannot run because the required disposable or system isolation boundary is unavailable. | Use a trusted ForgeLoop execution adapter with the required isolation mode; never run the check in the live project. |
923
950
  | `E_VERIFICATION_TOOL_UNAVAILABLE` | Required verification executable is missing in environment. | Use local equivalent, obtain host authority, or record NOT_VERIFIED. |
924
951
 
925
952
  <!-- END FORGELOOP GENERATED: public-error-codes -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cassiomc1/forgeloop",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Portable, verifiable engineering protocol for AI coding environments and developer workflows",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,7 +60,8 @@
60
60
  "docs/UNIVERSAL_INTEGRATION.md",
61
61
  "docs/DIAGNOSTIC_MODEL.md",
62
62
  "docs/EXECUTION_TRACE.md",
63
- "docs/RELEASE_CHECKLIST_1_5_MCP.md"
63
+ "docs/RELEASE_CHECKLIST_1_5_MCP.md",
64
+ "docs/RELEASE_CHECKLIST_1_6_1.md"
64
65
  ],
65
66
  "scripts": {
66
67
  "test": "node scripts/run-tests.js",
@@ -81,7 +82,9 @@
81
82
  "dependency:policy": "node scripts/check-dependency-policy.mjs",
82
83
  "mcp:test": "node --test integrations/mcp/tests/*.test.js",
83
84
  "mcp:setup": "node scripts/mcp-setup.mjs",
84
- "mcp:pack:check": "node scripts/mcp-package-smoke.mjs"
85
+ "mcp:pack:check": "node scripts/mcp-package-smoke.mjs",
86
+ "poc:evidence:verify": "node scripts/verify_poc_evidence.mjs",
87
+ "poc:evidence:test": "node --test poc/test/poc-evidence-publication.test.js"
85
88
  },
86
89
  "devDependencies": {
87
90
  "c8": "^12.0.0",
@@ -29,12 +29,27 @@
29
29
  "requirement": { "type": "string", "minLength": 1 },
30
30
  "verificationCycle": { "type": "integer", "minimum": 1 },
31
31
  "kind": { "const": "COMMAND_EXECUTION" },
32
+ "executionKind": { "enum": ["VERIFICATION", "DURABLE_ACTION"] },
32
33
  "argv": {
33
34
  "type": "array",
34
35
  "minItems": 1,
35
36
  "items": { "type": "string", "minLength": 1 }
36
37
  },
38
+ "protocolProjectRoot": { "type": "string", "minLength": 1 },
37
39
  "cwd": { "type": "string", "minLength": 1 },
40
+ "executionIsolation": { "enum": ["NATIVE_PROJECT", "PROJECT_ISOLATED", "SYSTEM_ISOLATED"] },
41
+ "isolation": {
42
+ "type": "object",
43
+ "required": ["mode", "isolated", "liveProjectWritable", "networkPolicy", "environmentPolicy"],
44
+ "properties": {
45
+ "mode": { "enum": ["NATIVE_PROJECT", "PROJECT_ISOLATED", "SYSTEM_ISOLATED"] },
46
+ "isolated": { "type": "boolean" },
47
+ "liveProjectWritable": { "type": "boolean" },
48
+ "networkPolicy": { "type": "string", "minLength": 1 },
49
+ "environmentPolicy": { "type": "string", "minLength": 1 }
50
+ },
51
+ "additionalProperties": false
52
+ },
38
53
  "resolution": {
39
54
  "type": "object",
40
55
  "required": ["resolutionMode", "mayInstall", "installer", "tool"],
@@ -48,6 +48,7 @@ export async function runCheck({
48
48
  timeoutMs,
49
49
  authorityContext,
50
50
  runtimeContext,
51
+ executionKind: "VERIFICATION",
51
52
  });
52
53
  const status = execution.execution.status === "passed" ? "passed" : "failed";
53
54
  const recorded = await recordCheckArtifact({
@@ -72,6 +72,7 @@ export async function executeDurableAction({
72
72
  target, packageRoot, taskId, checkId: `action:${action.actionId}`,
73
73
  requirement: input.requirement ?? `durable action ${action.actionId}`,
74
74
  prepared, timeoutMs,
75
+ executionKind: "DURABLE_ACTION",
75
76
  });
76
77
  } catch (error) {
77
78
  // Persistence uncertainty after the launch boundary can never downgrade a
@@ -152,10 +152,42 @@ export async function validateCheckExecutionProvenance(check, {
152
152
  requirement: check.requirement,
153
153
  verificationCycle: check.details?.verificationCycle ?? 1,
154
154
  });
155
- if (!allowForeignCwd && path.resolve(execution.cwd) !== path.resolve(target)) {
155
+ const protocolProjectRoot = execution.protocolProjectRoot ?? execution.cwd;
156
+ if (
157
+ execution.executionKind !== undefined
158
+ && execution.executionKind !== "VERIFICATION"
159
+ ) {
156
160
  throw artifactError(
157
161
  "E_EXECUTION_REF_INVALID",
158
- "Execution artifact cwd does not match the current target",
162
+ "A command check must reference a verification execution artifact",
163
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
164
+ );
165
+ }
166
+ if (
167
+ execution.executionIsolation !== undefined
168
+ && execution.isolation?.mode !== execution.executionIsolation
169
+ ) {
170
+ throw artifactError(
171
+ "E_EXECUTION_REF_INVALID",
172
+ "Execution artifact isolation mode does not match its isolation metadata",
173
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
174
+ );
175
+ }
176
+ if (
177
+ execution.isolation?.mode !== undefined
178
+ && execution.isolation.mode !== "NATIVE_PROJECT"
179
+ && path.resolve(execution.cwd) === path.resolve(protocolProjectRoot)
180
+ ) {
181
+ throw artifactError(
182
+ "E_EXECUTION_REF_INVALID",
183
+ "Isolated execution artifact cwd must be separate from the protocol project root",
184
+ [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
185
+ );
186
+ }
187
+ if (!allowForeignCwd && path.resolve(protocolProjectRoot) !== path.resolve(target)) {
188
+ throw artifactError(
189
+ "E_EXECUTION_REF_INVALID",
190
+ "Execution artifact protocol project root does not match the current target",
159
191
  [taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
160
192
  );
161
193
  }
@@ -6,6 +6,10 @@ import {
6
6
  E_INSTALLATION_AUTHORITY_REQUIRED,
7
7
  E_VERIFICATION_TOOL_UNAVAILABLE,
8
8
  } from "./verification-constants.js";
9
+ import {
10
+ E_VERIFICATION_EXECUTION_INVALID,
11
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
12
+ } from "./verification-execution.js";
9
13
 
10
14
  export const E_TASK_REQUIRED = "E_TASK_REQUIRED";
11
15
  export const E_TASK_NOT_FOUND = "E_TASK_NOT_FOUND";
@@ -145,6 +149,20 @@ export const PUBLIC_ERROR_CODES = Object.freeze({
145
149
  meaning: "Required verification executable is missing in environment.",
146
150
  safeResolution: "Use local equivalent, obtain host authority, or record NOT_VERIFIED.",
147
151
  }),
152
+ E_VERIFICATION_ISOLATION_UNAVAILABLE: Object.freeze({
153
+ code: "E_VERIFICATION_ISOLATION_UNAVAILABLE",
154
+ category: "verification-isolation",
155
+ classification: "PUBLIC_STABLE",
156
+ meaning: "Verification cannot run because the required disposable or system isolation boundary is unavailable.",
157
+ safeResolution: "Use a trusted ForgeLoop execution adapter with the required isolation mode; never run the check in the live project.",
158
+ }),
159
+ E_VERIFICATION_EXECUTION_INVALID: Object.freeze({
160
+ code: "E_VERIFICATION_EXECUTION_INVALID",
161
+ category: "verification-isolation",
162
+ classification: "PUBLIC_STABLE",
163
+ meaning: "The trusted verification execution adapter returned incomplete or invalid execution metadata.",
164
+ safeResolution: "Repair the adapter contract and rerun verification; do not promote incomplete execution evidence.",
165
+ }),
148
166
  E_INSTALLATION_AUTHORITY_REQUIRED: Object.freeze({
149
167
  code: "E_INSTALLATION_AUTHORITY_REQUIRED",
150
168
  category: "authority",
@@ -833,6 +851,8 @@ export const E_INIT_KIT_CONFLICT = "E_INIT_KIT_CONFLICT";
833
851
  export const ALL_KNOWN_ERROR_CODES = Object.freeze(new Set([
834
852
  ...FAILURE_CODES,
835
853
  E_VERIFICATION_TOOL_UNAVAILABLE,
854
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
855
+ E_VERIFICATION_EXECUTION_INVALID,
836
856
  E_INSTALLATION_AUTHORITY_REQUIRED,
837
857
  E_COMMAND_RESOLUTION_AMBIGUOUS,
838
858
  E_AUTHORITY_INVALID,
@@ -62,6 +62,7 @@ export async function runCommandExecution({
62
62
  runtimeContext,
63
63
  executionPath,
64
64
  timeoutMs = null,
65
+ executionKind = "VERIFICATION",
65
66
  } = {}) {
66
67
  const prepared = await prepareCommandExecution({
67
68
  target,
@@ -80,6 +81,8 @@ export async function runCommandExecution({
80
81
  prepared,
81
82
  timeoutMs,
82
83
  executionPath,
84
+ executionKind,
85
+ runtimeContext,
83
86
  });
84
87
  }
85
88
 
@@ -2,6 +2,7 @@ import { CLI_COMMAND_DEFINITIONS } from "./cli-command-definitions.js";
2
2
  import { COMMAND_EXECUTORS } from "./command-executors.js";
3
3
  import { PROTOCOL_VERSION } from "./protocol.js";
4
4
  import { FORGELOOP_INTEGRATION_RUNTIME_VERSION } from "./command-runtime.js";
5
+ import { VERIFICATION_ISOLATION_MODES } from "./verification-execution.js";
5
6
 
6
7
  /**
7
8
  * Integration risk classes. They classify the *invocation*, not only the
@@ -133,6 +134,13 @@ export function getForgeLoopCapabilities({ packageVersion = null } = {}) {
133
134
  readOnlyMetrics: true,
134
135
  projectLocalReference: true,
135
136
  },
137
+ verificationExecutionIsolation: {
138
+ version: 1,
139
+ supported: true,
140
+ adapter: true,
141
+ modes: [...VERIFICATION_ISOLATION_MODES],
142
+ protocolProjectRootSeparateFromExecutionCwd: true,
143
+ },
136
144
  },
137
145
  commands,
138
146
  resources: [
@@ -4,6 +4,7 @@ import {
4
4
  validateVerificationAuthority,
5
5
  E_COMMAND_RESOLUTION_AMBIGUOUS,
6
6
  } from "./verification-capability.js";
7
+ import { executeVerificationProcess } from "./verification-execution.js";
7
8
 
8
9
  export { E_COMMAND_RESOLUTION_AMBIGUOUS };
9
10
 
@@ -171,12 +172,36 @@ export async function runPreparedCommandExecution({
171
172
  prepared,
172
173
  timeoutMs = null,
173
174
  executionPath,
175
+ executionKind = "VERIFICATION",
176
+ runtimeContext,
174
177
  }) {
175
178
  const { createHash } = await import("node:crypto");
176
179
  const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
177
180
  const executionId = `exec-${randomUUID()}`;
178
181
  const startedAt = new Date().toISOString();
179
- const processResult = await executePreparedProcess(prepared.argv, target, { timeoutMs });
182
+ const processResult = executionKind === "VERIFICATION"
183
+ ? await executeVerificationProcess({
184
+ argv: prepared.argv,
185
+ protocolProjectRoot: target,
186
+ taskId,
187
+ checkId,
188
+ requirement,
189
+ resolution: prepared.resolution,
190
+ timeoutMs,
191
+ runtimeContext,
192
+ nativeExecute: executePreparedProcess,
193
+ })
194
+ : {
195
+ ...(await executePreparedProcess(prepared.argv, target, { timeoutMs })),
196
+ cwd: target,
197
+ isolation: {
198
+ mode: "NATIVE_PROJECT",
199
+ isolated: false,
200
+ liveProjectWritable: true,
201
+ networkPolicy: "INHERITED",
202
+ environmentPolicy: "INHERITED",
203
+ },
204
+ };
180
205
  const finishedAt = new Date().toISOString();
181
206
  const execution = {
182
207
  schemaVersion: 1,
@@ -187,8 +212,10 @@ export async function runPreparedCommandExecution({
187
212
  requirement,
188
213
  verificationCycle,
189
214
  kind: "COMMAND_EXECUTION",
215
+ executionKind,
190
216
  argv: prepared.argv,
191
- cwd: target,
217
+ protocolProjectRoot: target,
218
+ cwd: processResult.cwd,
192
219
  resolution: {
193
220
  resolutionMode: prepared.resolution.resolutionMode,
194
221
  mayInstall: prepared.resolution.mayInstall,
@@ -203,6 +230,8 @@ export async function runPreparedCommandExecution({
203
230
  durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt)),
204
231
  termination: processResult.spawnError ? "spawn-error" : processResult.timedOut ? "timeout" : processResult.signal ? "signal" : "exit",
205
232
  signal: processResult.signal ?? null,
233
+ executionIsolation: processResult.isolation.mode,
234
+ isolation: processResult.isolation,
206
235
  stdoutSha256: digest(processResult.stdout),
207
236
  stderrSha256: digest(processResult.stderr),
208
237
  stdoutBytes: processResult.stdoutBytes,
@@ -3,6 +3,7 @@ import { ARTIFACT_REGISTRY } from "./artifact-registry.js";
3
3
  import { PUBLIC_ERROR_REGISTRY } from "./error-codes.js";
4
4
  import { GUIDE_REGISTRY } from "./guide-registry.js";
5
5
  import { PROTOCOL_VERSION, WORK_PHASES, WORK_TRANSITIONS } from "./protocol.js";
6
+ import { VERIFICATION_ISOLATION_MODES } from "./verification-execution.js";
6
7
 
7
8
  export const SCHEMA_COMPATIBILITY_POLICY = Object.freeze({
8
9
  protocolVersion: PROTOCOL_VERSION,
@@ -50,6 +51,13 @@ export function protocolInfo({ packageVersion = null } = {}) {
50
51
  supported: true,
51
52
  schemaVersion: 1,
52
53
  },
54
+ verificationExecutionIsolation: {
55
+ version: 1,
56
+ supported: true,
57
+ adapter: true,
58
+ modes: [...VERIFICATION_ISOLATION_MODES],
59
+ protocolProjectRootSeparateFromExecutionCwd: true,
60
+ },
53
61
  structuredTrace: {
54
62
  version: 1,
55
63
  supported: true,
@@ -1,3 +1,9 @@
1
+ import {
2
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
3
+ isVerificationExecutionAdapter,
4
+ normalizeVerificationExecutionPolicy,
5
+ } from "./verification-execution.js";
6
+
1
7
  export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
2
8
 
3
9
  const AUTHORITY_CONTEXT_FIELDS = Object.freeze([
@@ -76,5 +82,17 @@ export function resolveAuthorityContext(options = {}) {
76
82
 
77
83
  export function createForgeLoopContext(options = {}) {
78
84
  const authorityContext = createAuthorityContext(options);
79
- return Object.freeze({ authorityContext });
85
+ const context = { authorityContext };
86
+ if (options?.verificationExecutionAdapter !== undefined) {
87
+ if (!isVerificationExecutionAdapter(options.verificationExecutionAdapter)) {
88
+ const error = new Error("Verification execution adapter is unavailable or invalid");
89
+ error.code = E_VERIFICATION_ISOLATION_UNAVAILABLE;
90
+ throw error;
91
+ }
92
+ context.verificationExecutionAdapter = options.verificationExecutionAdapter;
93
+ }
94
+ if (options?.verificationExecutionPolicy !== undefined) {
95
+ context.verificationExecutionPolicy = normalizeVerificationExecutionPolicy(options.verificationExecutionPolicy);
96
+ }
97
+ return Object.freeze(context);
80
98
  }
@@ -0,0 +1,257 @@
1
+ export const VERIFICATION_ISOLATION_MODES = Object.freeze([
2
+ "NATIVE_PROJECT",
3
+ "PROJECT_ISOLATED",
4
+ "SYSTEM_ISOLATED",
5
+ ]);
6
+
7
+ export const VERIFICATION_EXECUTION_POLICY_MODES = Object.freeze([
8
+ "NONE",
9
+ ...VERIFICATION_ISOLATION_MODES,
10
+ ]);
11
+
12
+ export const E_VERIFICATION_ISOLATION_UNAVAILABLE = "E_VERIFICATION_ISOLATION_UNAVAILABLE";
13
+ export const E_VERIFICATION_EXECUTION_INVALID = "E_VERIFICATION_EXECUTION_INVALID";
14
+
15
+ const ISOLATION_RANK = Object.freeze({
16
+ NATIVE_PROJECT: 0,
17
+ PROJECT_ISOLATED: 1,
18
+ SYSTEM_ISOLATED: 2,
19
+ });
20
+
21
+ function isRecord(value) {
22
+ return value !== null && typeof value === "object" && !Array.isArray(value);
23
+ }
24
+
25
+ function executionError(code, message) {
26
+ const error = new Error(message);
27
+ error.code = code;
28
+ return error;
29
+ }
30
+
31
+ function requiredString(value, field) {
32
+ if (typeof value !== "string" || value.trim() === "") {
33
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be a non-empty string`);
34
+ }
35
+ return value;
36
+ }
37
+
38
+ function nullableString(value, field) {
39
+ if (value !== null && typeof value !== "string") {
40
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be a string or null`);
41
+ }
42
+ return value;
43
+ }
44
+
45
+ function outputBytes(value, field) {
46
+ if (value === undefined || value === null) return Buffer.alloc(0);
47
+ if (typeof value === "string") return Buffer.from(value, "utf8");
48
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value);
49
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be text or bytes`);
50
+ }
51
+
52
+ function nonNegativeInteger(value, field, fallback) {
53
+ const resolved = value === undefined ? fallback : value;
54
+ if (!Number.isInteger(resolved) || resolved < 0) {
55
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be a non-negative integer`);
56
+ }
57
+ return resolved;
58
+ }
59
+
60
+ export function normalizeVerificationExecutionPolicy(policy = {}) {
61
+ if (policy === undefined || policy === null) return Object.freeze({ requiredIsolation: "NONE" });
62
+ if (!isRecord(policy)) {
63
+ throw executionError(E_VERIFICATION_ISOLATION_UNAVAILABLE, "Verification execution isolation policy is invalid");
64
+ }
65
+ const requiredIsolation = policy.requiredIsolation ?? "NONE";
66
+ if (!VERIFICATION_EXECUTION_POLICY_MODES.includes(requiredIsolation)) {
67
+ throw executionError(E_VERIFICATION_ISOLATION_UNAVAILABLE, "Verification execution isolation policy is unsupported");
68
+ }
69
+ return Object.freeze({ requiredIsolation });
70
+ }
71
+
72
+ export function isVerificationExecutionAdapter(value) {
73
+ return isRecord(value) && typeof value.execute === "function";
74
+ }
75
+
76
+ function readAdapter(runtimeContext) {
77
+ const value = isRecord(runtimeContext) ? runtimeContext.verificationExecutionAdapter : undefined;
78
+ if (value === undefined || value === null) return null;
79
+ if (!isVerificationExecutionAdapter(value)) {
80
+ throw executionError(E_VERIFICATION_ISOLATION_UNAVAILABLE, "Verification execution adapter is unavailable or invalid");
81
+ }
82
+ return value;
83
+ }
84
+
85
+ function assertIsolationMetadataConsistency(isolation) {
86
+ if (isolation.mode === "NATIVE_PROJECT") {
87
+ if (
88
+ isolation.isolated !== false
89
+ || isolation.liveProjectWritable !== true
90
+ ) {
91
+ throw executionError(
92
+ E_VERIFICATION_EXECUTION_INVALID,
93
+ "NATIVE_PROJECT must report isolated=false and liveProjectWritable=true",
94
+ );
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (
100
+ isolation.isolated !== true
101
+ || isolation.liveProjectWritable !== false
102
+ ) {
103
+ throw executionError(
104
+ E_VERIFICATION_EXECUTION_INVALID,
105
+ `${isolation.mode} must report isolated=true and liveProjectWritable=false`,
106
+ );
107
+ }
108
+
109
+ if (
110
+ isolation.mode === "SYSTEM_ISOLATED"
111
+ && isolation.networkPolicy !== "DENIED"
112
+ ) {
113
+ throw executionError(
114
+ E_VERIFICATION_EXECUTION_INVALID,
115
+ "SYSTEM_ISOLATED must report networkPolicy=DENIED",
116
+ );
117
+ }
118
+ }
119
+
120
+ function normalizeIsolation(value) {
121
+ if (!isRecord(value)) {
122
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution isolation metadata is missing");
123
+ }
124
+ const mode = value.mode;
125
+ if (!VERIFICATION_ISOLATION_MODES.includes(mode)) {
126
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution isolation mode is unsupported");
127
+ }
128
+ if (typeof value.isolated !== "boolean" || typeof value.liveProjectWritable !== "boolean") {
129
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution isolation booleans are invalid");
130
+ }
131
+ const isolation = {
132
+ mode,
133
+ isolated: value.isolated,
134
+ liveProjectWritable: value.liveProjectWritable,
135
+ networkPolicy: requiredString(value.networkPolicy, "networkPolicy"),
136
+ environmentPolicy: requiredString(value.environmentPolicy, "environmentPolicy"),
137
+ };
138
+ assertIsolationMetadataConsistency(isolation);
139
+ return isolation;
140
+ }
141
+
142
+ export function normalizeVerificationExecutionResult(value, { defaultCwd = null } = {}) {
143
+ if (!isRecord(value)) {
144
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution adapter returned an invalid result");
145
+ }
146
+ const cwd = value.cwd ?? defaultCwd;
147
+ requiredString(cwd, "cwd");
148
+ if (value.exitCode !== null && !Number.isInteger(value.exitCode)) {
149
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution exitCode must be an integer or null");
150
+ }
151
+ const signal = nullableString(value.signal ?? null, "signal");
152
+ if (typeof value.timedOut !== "boolean") {
153
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution timedOut must be boolean");
154
+ }
155
+ if (typeof value.outputTruncated !== "boolean") {
156
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution outputTruncated must be boolean");
157
+ }
158
+ const stdout = outputBytes(value.stdout, "stdout");
159
+ const stderr = outputBytes(value.stderr, "stderr");
160
+ return {
161
+ exitCode: value.exitCode,
162
+ signal,
163
+ timedOut: value.timedOut,
164
+ stdout,
165
+ stderr,
166
+ stdoutBytes: nonNegativeInteger(value.stdoutBytes, "stdoutBytes", stdout.length),
167
+ stderrBytes: nonNegativeInteger(value.stderrBytes, "stderrBytes", stderr.length),
168
+ outputTruncated: value.outputTruncated,
169
+ spawnError: Boolean(value.spawnError),
170
+ cwd,
171
+ isolation: normalizeIsolation(value.isolation),
172
+ };
173
+ }
174
+
175
+ function satisfiesIsolationPolicy(isolation, requiredIsolation) {
176
+ if (requiredIsolation === "NONE") return true;
177
+ if (ISOLATION_RANK[isolation.mode] < ISOLATION_RANK[requiredIsolation]) return false;
178
+ if (requiredIsolation === "NATIVE_PROJECT") return true;
179
+ return isolation.isolated === true
180
+ && isolation.liveProjectWritable === false
181
+ && (requiredIsolation !== "SYSTEM_ISOLATED" || isolation.networkPolicy === "DENIED");
182
+ }
183
+
184
+ export function assertVerificationIsolation(result, policy) {
185
+ const requiredIsolation = normalizeVerificationExecutionPolicy(policy).requiredIsolation;
186
+ if (!satisfiesIsolationPolicy(result.isolation, requiredIsolation)) {
187
+ throw executionError(
188
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
189
+ `Verification execution did not satisfy the required ${requiredIsolation} isolation boundary`,
190
+ );
191
+ }
192
+ return result;
193
+ }
194
+
195
+ export async function executeVerificationProcess({
196
+ argv,
197
+ protocolProjectRoot,
198
+ taskId,
199
+ checkId,
200
+ requirement,
201
+ resolution,
202
+ timeoutMs,
203
+ runtimeContext,
204
+ nativeExecute,
205
+ }) {
206
+ const policy = normalizeVerificationExecutionPolicy(runtimeContext?.verificationExecutionPolicy);
207
+ const adapter = readAdapter(runtimeContext);
208
+ let rawResult;
209
+ if (adapter) {
210
+ try {
211
+ rawResult = await adapter.execute(Object.freeze({
212
+ argv: [...argv],
213
+ protocolProjectRoot,
214
+ taskId,
215
+ checkId,
216
+ requirement,
217
+ timeoutMs,
218
+ resolution,
219
+ }));
220
+ } catch (error) {
221
+ if (error?.code === E_VERIFICATION_ISOLATION_UNAVAILABLE) throw error;
222
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution adapter failed");
223
+ }
224
+ } else {
225
+ if (policy.requiredIsolation !== "NONE") {
226
+ throw executionError(
227
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
228
+ `Verification execution requires ${policy.requiredIsolation}, but no trusted adapter was supplied`,
229
+ );
230
+ }
231
+ const nativeResult = await nativeExecute(argv, protocolProjectRoot, { timeoutMs });
232
+ rawResult = {
233
+ ...nativeResult,
234
+ cwd: protocolProjectRoot,
235
+ isolation: {
236
+ mode: "NATIVE_PROJECT",
237
+ isolated: false,
238
+ liveProjectWritable: true,
239
+ networkPolicy: "INHERITED",
240
+ environmentPolicy: "INHERITED",
241
+ },
242
+ };
243
+ }
244
+
245
+ const normalized = normalizeVerificationExecutionResult(rawResult, { defaultCwd: protocolProjectRoot });
246
+ if (
247
+ policy.requiredIsolation !== "NONE"
248
+ && normalized.isolation.mode !== "NATIVE_PROJECT"
249
+ && normalized.cwd === protocolProjectRoot
250
+ ) {
251
+ throw executionError(
252
+ E_VERIFICATION_EXECUTION_INVALID,
253
+ "Isolated verification execution must use a cwd separate from the protocol project root",
254
+ );
255
+ }
256
+ return assertVerificationIsolation(normalized, policy);
257
+ }
@@ -44,4 +44,12 @@ export {
44
44
  CLI_COMMAND_DEFINITIONS,
45
45
  };
46
46
 
47
+ export { createForgeLoopContext } from "./core/runtime-context.js";
48
+ export {
49
+ E_VERIFICATION_EXECUTION_INVALID,
50
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
51
+ VERIFICATION_EXECUTION_POLICY_MODES,
52
+ VERIFICATION_ISOLATION_MODES,
53
+ } from "./core/verification-execution.js";
54
+
47
55
  export const FORGELOOP_INTEGRATION_API_VERSION = FORGELOOP_INTEGRATION_RUNTIME_VERSION;