@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.
- package/CONTRACT_COVERAGE.md +1 -0
- package/DOCS_INDEX.md +7 -2
- package/LOOP_ENGINEERING.md +22 -1
- package/PROTOCOL_INTEGRATION.md +44 -0
- package/README.md +16 -0
- package/THREAT_MODEL.md +2 -1
- package/docs/ARTIFACT_REFERENCE.md +19 -0
- package/docs/MCP.md +1 -1
- package/docs/RECIPES.md +1 -1
- package/docs/RELEASE_CHECKLIST_1_6_1.md +121 -0
- package/docs/TROUBLESHOOTING.md +27 -0
- package/package.json +6 -3
- package/schemas/execution.schema.json +15 -0
- package/src/commands/run-check.js +1 -0
- package/src/core/action-execution.js +1 -0
- package/src/core/completion-artifacts.js +34 -2
- package/src/core/error-codes.js +20 -0
- package/src/core/execution.js +3 -0
- package/src/core/integration-invocation-policy.js +8 -0
- package/src/core/prepared-execution.js +31 -2
- package/src/core/protocol-info.js +8 -0
- package/src/core/runtime-context.js +19 -1
- package/src/core/verification-execution.js +257 -0
- package/src/integration.js +8 -0
package/CONTRACT_COVERAGE.md
CHANGED
|
@@ -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.
|
|
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/
|
|
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)
|
package/LOOP_ENGINEERING.md
CHANGED
|
@@ -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
|
|
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`;
|
package/PROTOCOL_INTEGRATION.md
CHANGED
|
@@ -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
|
|
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 |
|
|
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 --
|
|
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`.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -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.
|
|
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"],
|
|
@@ -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
|
-
|
|
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
|
-
"
|
|
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
|
}
|
package/src/core/error-codes.js
CHANGED
|
@@ -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,
|
package/src/core/execution.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/integration.js
CHANGED
|
@@ -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;
|