@cassiomc1/forgeloop 2.0.0-rc.2 → 2.0.0-rc.5
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/DOCS_INDEX.md +1 -0
- package/README.md +16 -13
- package/docs/AGENT_PROTOCOL_SUMMARY.md +1 -1
- package/docs/AGENT_SKILL.md +15 -0
- package/docs/FORGELOOP_AUDIT.md +71 -0
- package/docs/GETTING_STARTED.md +6 -0
- package/docs/OPENSRC_ADAPTER.md +1 -1
- package/docs/PACKAGE_CONTENTS.md +7 -2
- package/docs/RELEASE_CHECKLIST.md +80 -4
- package/docs/TROUBLESHOOTING.md +35 -1
- package/docs/documentation-manifest.json +11 -0
- package/package.json +3 -2
- package/scripts/generate-forgeloop-skill.mjs +6 -0
- package/skills/forgeloop/SKILL.md +6 -0
- package/src/core/completion-artifacts.js +14 -7
- package/src/repository-index/binary-manager.js +8 -2
- package/src/storage/migration-validation.js +3 -3
- package/src/storage/project-replacement.js +3 -3
- package/src/storage/project-restore.js +3 -3
- package/src/storage/restore-execution-provenance.js +26 -0
- package/src/storage/restore-replacement-archive.js +1 -1
- package/src/storage/restore-replacement.js +1 -1
- package/src/storage/unit-of-work.js +6 -2
package/DOCS_INDEX.md
CHANGED
|
@@ -36,6 +36,7 @@ integration and guide context. Use this map before editing documentation.
|
|
|
36
36
|
| Platform adapters | [`docs/PLATFORM_ADAPTERS.md`](./docs/PLATFORM_ADAPTERS.md) | Generic CI boundary and platform mapping guidance |
|
|
37
37
|
| Universal integration API | [`docs/UNIVERSAL_INTEGRATION.md`](./docs/UNIVERSAL_INTEGRATION.md) | Programmatic integration subpath, envelope semantics, and consumer map |
|
|
38
38
|
| Audit UX read model | [`docs/AUDIT_UX.md`](./docs/AUDIT_UX.md) | Bounded read-only task timeline, health, verification, ownership, and completion projection |
|
|
39
|
+
| ForgeLoopAudit workspace | [`docs/FORGELOOP_AUDIT.md`](./docs/FORGELOOP_AUDIT.md) | In-repository Audit setup, project inspection, view walkthrough, screenshots and verification links |
|
|
39
40
|
| Advisory context providers | [`docs/ADVISORY_CONTEXT.md`](./docs/ADVISORY_CONTEXT.md) | Optional external host context, non-evidence trust boundary, allowlist normalization, and safety rules |
|
|
40
41
|
| Provider extension architecture | [`docs/PROVIDER_ARCHITECTURE.md`](./docs/PROVIDER_ARCHITECTURE.md) | Provider-neutral architecture, invocation lifecycle, strict snapshots, cancellation, trust, and authority boundaries |
|
|
41
42
|
| Provider extension reference | [`docs/PROVIDERS.md`](./docs/PROVIDERS.md) | Capability discovery, provider kinds, common contract, limits, errors, and maintainer checklist |
|
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
[](https://github.com/cassiomc1/forgeloop/actions/workflows/package-smoke.yml)
|
|
12
12
|
[](https://github.com/cassiomc1/forgeloop/actions/workflows/release-notes.yml)
|
|
13
13
|
|
|
14
|
-
ForgeLoop
|
|
14
|
+
ForgeLoop governs AI-assisted engineering. Jev supplies bounded semantic input; the host model implements changes. ForgeLoop owns lifecycle, claims, gates, evidence, completion, recovery, and publication truth.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Sources: [`DOCS_INDEX.md`](./DOCS_INDEX.md); canonical [`LOOP_ENGINEERING.md`](./LOOP_ENGINEERING.md); discovery [`PROTOCOL_INTEGRATION.md`](./PROTOCOL_INTEGRATION.md); project facts [`PROJECT_PROFILE.md`](./PROJECT_PROFILE.md); guides [`GUIDE_ROUTER.md`](./GUIDE_ROUTER.md).
|
|
17
17
|
|
|
18
18
|
## Where should I start?
|
|
19
19
|
|
|
@@ -33,7 +33,6 @@ Operational sources: [`DOCS_INDEX.md`](./DOCS_INDEX.md); [`LOOP_ENGINEERING.md`]
|
|
|
33
33
|
- **Troubleshooting & error codes** → [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md)
|
|
34
34
|
- **Code attestation & revision coverage** → [`docs/CODE_ATTESTATION.md`](./docs/CODE_ATTESTATION.md)
|
|
35
35
|
- **System architecture & safety** → [`LOOP_SYSTEM_DESIGN.md`](./LOOP_SYSTEM_DESIGN.md) & [`THREAT_MODEL.md`](./THREAT_MODEL.md)
|
|
36
|
-
- **Documentation index & ownership** → [`DOCS_INDEX.md`](./DOCS_INDEX.md)
|
|
37
36
|
|
|
38
37
|
## Real execution proof
|
|
39
38
|
|
|
@@ -78,7 +77,8 @@ not establish project identity. Public project evidence remains v1.
|
|
|
78
77
|
## Distribution and license
|
|
79
78
|
|
|
80
79
|
GitHub hosts source and issues. The core npm package is publicly available;
|
|
81
|
-
the MCP companion retains restricted distribution.
|
|
80
|
+
the MCP companion retains restricted distribution. RC5 targets `next`; `latest`
|
|
81
|
+
remains at RC4. Stable SQLite acceptance is incomplete.
|
|
82
82
|
ForgeLoop-owned source and documentation are licensed under the
|
|
83
83
|
[PolyForm Noncommercial License 1.0.0](./LICENSE). Commercial use is not granted
|
|
84
84
|
by this license. Third-party notices are documented in
|
|
@@ -88,7 +88,7 @@ by this license. Third-party notices are documented in
|
|
|
88
88
|
|
|
89
89
|
### Installation
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
Install the public core package from npm; no organization access is required:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
npm install --global @cassiomc1/forgeloop
|
|
@@ -203,7 +203,7 @@ forgeloop protocol-info --json
|
|
|
203
203
|
| Validate completion | Yes | Provides real work and evidence |
|
|
204
204
|
| Schedule agents or LLM inference | No | External to the protocol |
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
Initialize a target project using the public npm package:
|
|
207
207
|
|
|
208
208
|
```bash
|
|
209
209
|
npx @cassiomc1/forgeloop init
|
|
@@ -246,7 +246,7 @@ host-reported or `UNKNOWN`; values are never estimated.
|
|
|
246
246
|
capability, but advisory recall remains Integration API only. The stock CLI
|
|
247
247
|
does not auto-recall providers and does not expose a `context-recall` command.
|
|
248
248
|
|
|
249
|
-
Validate the source checkout
|
|
249
|
+
Validate the source checkout before preparing a release:
|
|
250
250
|
|
|
251
251
|
```bash
|
|
252
252
|
node src/cli.js init
|
|
@@ -495,6 +495,12 @@ CI audits each supplied task receipt explicitly. With no receipt it reports
|
|
|
495
495
|
completion or attestation. For contributor checks and safe payload retention,
|
|
496
496
|
see [CONTRIBUTING.md](./CONTRIBUTING.md#focused-verification-and-maintenance).
|
|
497
497
|
|
|
498
|
+
## ForgeLoopAudit workspace
|
|
499
|
+
|
|
500
|
+
The read-only auditor lives in `ForgeLoopAudit/`. See
|
|
501
|
+
[setup, usage and screenshots](./docs/FORGELOOP_AUDIT.md) for the demo,
|
|
502
|
+
project inspection and audit views.
|
|
503
|
+
|
|
498
504
|
## Protocol compatibility
|
|
499
505
|
|
|
500
506
|
The npm package version is independent of protocol version. The current
|
|
@@ -615,17 +621,15 @@ docs/diagrams/ typed Archify diagram source and inventory
|
|
|
615
621
|
docs/assets/diagrams/ committed HTML, SVG, and generation receipt
|
|
616
622
|
scripts/ checks, renderer, release identity, CI notes
|
|
617
623
|
tests/ Node and Python regression coverage
|
|
618
|
-
.forgeloop/ project-scoped
|
|
624
|
+
.forgeloop/ project-scoped configuration
|
|
619
625
|
.forgeloop/task-state/ isolated live task protocol state
|
|
620
626
|
DOCS_INDEX.md documentation map and ownership boundaries
|
|
621
627
|
```
|
|
622
628
|
|
|
623
|
-
Project-scoped configuration remains under `.forgeloop/`.
|
|
624
629
|
Task-scoped mutable protocol state is stored under
|
|
625
630
|
`.forgeloop/task-state/<taskKey>/`.
|
|
626
631
|
|
|
627
|
-
For
|
|
628
|
-
details, start at [`DOCS_INDEX.md`](./DOCS_INDEX.md).
|
|
632
|
+
For documentation ownership and integrations, see [`DOCS_INDEX.md`](./DOCS_INDEX.md).
|
|
629
633
|
|
|
630
634
|
## Programmatic integration and MCP
|
|
631
635
|
|
|
@@ -635,8 +639,7 @@ ForgeLoop exposes a stable programmatic surface:
|
|
|
635
639
|
import { executeForgeLoopCommand } from "@cassiomc1/forgeloop/integration";
|
|
636
640
|
```
|
|
637
641
|
|
|
638
|
-
The local MCP server (`@cassiomc1/forgeloop-mcp`, stdio)
|
|
639
|
-
this exact API — it never reimplements ForgeLoop. See
|
|
642
|
+
The local MCP server (`@cassiomc1/forgeloop-mcp`, stdio) wraps this API. See
|
|
640
643
|
[`docs/UNIVERSAL_INTEGRATION.md`](./docs/UNIVERSAL_INTEGRATION.md) and
|
|
641
644
|
[`docs/MCP.md`](./docs/MCP.md). MCP is optional; the CLI and instruction-only
|
|
642
645
|
hosts remain fully supported without it.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
ForgeLoop is a portable protocol and support CLI for verifiable engineering workflows. It records and validates task state, contracts, routing, checks, evidence, continuity, and optional code attestations. It does not become an agent scheduler, delegation service, source-control authority, or secret manager.
|
|
8
8
|
|
|
9
9
|
Protocol version: 1
|
|
10
|
-
Package version: 2.0.0-rc.
|
|
10
|
+
Package version: 2.0.0-rc.5
|
|
11
11
|
|
|
12
12
|
## Canonical loop
|
|
13
13
|
|
package/docs/AGENT_SKILL.md
CHANGED
|
@@ -48,6 +48,21 @@ behavioral certification for every external harness. Existing native
|
|
|
48
48
|
`AGENTS.md`, `CLAUDE.md`, Cursor, and Copilot integration surfaces remain
|
|
49
49
|
intact.
|
|
50
50
|
|
|
51
|
+
## ForgeLoopAudit workspace guidance
|
|
52
|
+
|
|
53
|
+
The generated Skill documents the Audit component at `ForgeLoopAudit/` inside
|
|
54
|
+
the ForgeLoop repository, replacing the standalone repository and
|
|
55
|
+
`../ForgeLoopAudit`. It directs agents to
|
|
56
|
+
resolve the target project root before running commands: core ForgeLoop uses
|
|
57
|
+
the parent root; Audit uses its own directory, instructions, package scripts,
|
|
58
|
+
and configured protocol runtime. ForgeLoop is the source and contribution home
|
|
59
|
+
for both components; their package commands, task state, and receipt provenance
|
|
60
|
+
remain scoped to the target root. Audit remains a read-only auditor.
|
|
61
|
+
|
|
62
|
+
Maintain this guidance in `scripts/generate-forgeloop-skill.mjs`, then run
|
|
63
|
+
`npm run skill:generate` and `npm run skill:check`. An independently installed
|
|
64
|
+
Skill must be updated separately; generation only writes the repository copy.
|
|
65
|
+
|
|
51
66
|
## Safety Boundaries
|
|
52
67
|
|
|
53
68
|
Agents must discover tasks first, follow `next`, respect claims, require
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ForgeLoopAudit — setup and usage
|
|
2
|
+
|
|
3
|
+
ForgeLoopAudit is the local, read-only web interface for inspecting ForgeLoop
|
|
4
|
+
projects. It lives in [`ForgeLoopAudit/`](https://github.com/cassiomc1/forgeloop/blob/main/ForgeLoopAudit/README.md) in this
|
|
5
|
+
repository; the standalone repository is retired. Use it to inspect task
|
|
6
|
+
progress, findings, verification evidence, quality, history and reports.
|
|
7
|
+
ForgeLoop remains the authority for lifecycle and completion decisions.
|
|
8
|
+
|
|
9
|
+
## Start ForgeLoopAudit
|
|
10
|
+
|
|
11
|
+
Use Node.js **24.19.0 or newer**. From a checkout of this repository:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd ForgeLoopAudit
|
|
15
|
+
npm ci
|
|
16
|
+
npm run build:web
|
|
17
|
+
node dist/server/cli.mjs --demo
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The CLI starts a loopback-only server and opens the browser. If it does not
|
|
21
|
+
open automatically, use the one-time bootstrap URL printed in the terminal.
|
|
22
|
+
Keep the terminal running while using Audit; press `Ctrl+C` to stop the server.
|
|
23
|
+
|
|
24
|
+
To inspect your own ForgeLoop project, replace the demo command with an
|
|
25
|
+
explicit project path, still from `ForgeLoopAudit/`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node dist/server/cli.mjs --project /absolute/path/to/your-project
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To inspect the parent ForgeLoop checkout itself, use `--project ..`.
|
|
32
|
+
Audit reads native SQLite task state through ForgeLoop's public Integration API.
|
|
33
|
+
Legacy JSON projects open as `ARTIFACT_ONLY`; Audit does not migrate them or
|
|
34
|
+
change task state. Project selection stays with the local server rather than
|
|
35
|
+
uploading a repository through the browser.
|
|
36
|
+
|
|
37
|
+
## Explore the audit views
|
|
38
|
+
|
|
39
|
+
1. Open **Audit Summary** for integrity, completion readiness, quality and trust
|
|
40
|
+
verdicts. Use **Run audit** to refresh the read-only assessment.
|
|
41
|
+
2. Use **Projects** and **Task Board** to navigate registered projects and tasks
|
|
42
|
+
by canonical lifecycle phase; open **Tasks** for a task's detailed audit.
|
|
43
|
+
3. Review **Findings** and **Evidence** to trace warnings and verification gaps.
|
|
44
|
+
Coverage, attestation and completion are separate signals.
|
|
45
|
+
4. Inspect **Quality**, **Policy & Trust**, **Audit History** and **Diagnostics**
|
|
46
|
+
for supporting context. Use **Reports** for exports to managed local storage.
|
|
47
|
+
**Repository Search** is discovery only, not verification evidence.
|
|
48
|
+
|
|
49
|
+
## Screenshots
|
|
50
|
+
|
|
51
|
+
These are existing captures of the ForgeShop demo, taken from the production
|
|
52
|
+
renderer at 1440 × 900 in the dark theme, aligned to Audit `0.3.0-rc.3` and
|
|
53
|
+
ForgeLoop `2.0.0-rc.4`. The demo deliberately includes invalid, stale, blocked and
|
|
54
|
+
unverified scenarios. Its warnings and coverage values do not establish
|
|
55
|
+
completion or production readiness for your project.
|
|
56
|
+
|
|
57
|
+
| Audit Summary | Task Board |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
|  |  |
|
|
60
|
+
| Findings | Evidence |
|
|
61
|
+
|  |  |
|
|
62
|
+
|
|
63
|
+
See the [complete screenshot gallery](https://github.com/cassiomc1/forgeloop/blob/main/ForgeLoopAudit/README.md#screenshots)
|
|
64
|
+
and [capture provenance](https://github.com/cassiomc1/forgeloop/blob/main/ForgeLoopAudit/screen/README.md) for the full set.
|
|
65
|
+
For development, verification and command-root details, see the
|
|
66
|
+
[Audit README](https://github.com/cassiomc1/forgeloop/blob/main/ForgeLoopAudit/README.md),
|
|
67
|
+
[Audit documentation index](https://github.com/cassiomc1/forgeloop/blob/main/ForgeLoopAudit/docs/README.md) and
|
|
68
|
+
[workspace guidance](./AGENT_SKILL.md#forgeloopaudit-workspace-guidance).
|
|
69
|
+
Audit has its own dependencies, build and verification commands. Its application
|
|
70
|
+
source and screenshots are excluded from the core npm package; npm users can
|
|
71
|
+
[read this section and its images on GitHub](https://github.com/cassiomc1/forgeloop#forgeloopaudit-workspace).
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -117,6 +117,12 @@ and numeric limits.
|
|
|
117
117
|
|
|
118
118
|
### Global installation
|
|
119
119
|
|
|
120
|
+
The public core package is available without organization access. As of
|
|
121
|
+
2026-10-14, both npm `latest` and `next` resolve to `2.0.0-rc.4`. The default
|
|
122
|
+
installation below uses `latest`; use `@cassiomc1/forgeloop@2.0.0-rc.4` when an
|
|
123
|
+
exact reproducible version is required. RC4 remains a release candidate;
|
|
124
|
+
its passing new-user checks do not establish stable SQLite acceptance.
|
|
125
|
+
|
|
120
126
|
Install the ForgeLoop CLI globally to make the `forgeloop` command available
|
|
121
127
|
in your terminal:
|
|
122
128
|
|
package/docs/OPENSRC_ADAPTER.md
CHANGED
|
@@ -235,7 +235,7 @@ are added; `providerExtensions.publicRegistryApi` and
|
|
|
235
235
|
- Protocol v1, schema v1, Integration API v1 unchanged.
|
|
236
236
|
- `advisoryContextProviders` v1 and `providerExtensions` v1 unchanged; the
|
|
237
237
|
`ADVISORY_CONTEXT` provider kind covers this adapter.
|
|
238
|
-
-
|
|
238
|
+
- The ForgeLoop host requires Node.js 24.19.0+ with built-in `node:sqlite`.
|
|
239
239
|
- Upstream behaviors referenced: `opensrc path <source> [--cwd]`,
|
|
240
240
|
`OPENSRC_HOME` override, fetch-on-miss, `opensrc 0.7.3` observed at
|
|
241
241
|
authoring time (never hardcoded into behavior).
|
package/docs/PACKAGE_CONTENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Core npm package contents
|
|
2
2
|
|
|
3
|
-
This page describes the intentional boundary of the
|
|
3
|
+
This page describes the intentional boundary of the publicly distributed
|
|
4
4
|
`@cassiomc1/forgeloop` package. The [`files`](../package.json) list in
|
|
5
5
|
`package.json` is the packaging source of truth, while the repository's
|
|
6
6
|
`tests/package.test.js` and clean-room `scripts/package_smoke.mjs` checks keep
|
|
@@ -13,7 +13,9 @@ The package exposes the `forgeloop` executable from `src/cli.js` and the
|
|
|
13
13
|
`@cassiomc1/forgeloop/integration` subpath from `src/integration.js`, with its
|
|
14
14
|
declaration file. The package has the approved exact `@typesafe-ai/sdk`
|
|
15
15
|
dependency for the pinned semantic decision plane and `smol-toml` for bounded
|
|
16
|
-
Cargo manifest parsing. It requires Node.js
|
|
16
|
+
Cargo manifest parsing. It requires Node.js 24.19.0 or newer with built-in
|
|
17
|
+
`node:sqlite`, matching `package.json#engines`. The MCP companion retains
|
|
18
|
+
restricted distribution.
|
|
17
19
|
|
|
18
20
|
## Included files
|
|
19
21
|
|
|
@@ -90,6 +92,9 @@ The published tarball includes the following consumer-facing groups:
|
|
|
90
92
|
|
|
91
93
|
The tarball intentionally omits repository-only material:
|
|
92
94
|
|
|
95
|
+
- the `ForgeLoopAudit/` application source, dependencies, builds, and application
|
|
96
|
+
package; maintaining it inside the repository does not add it to the core
|
|
97
|
+
package's `files` allowlist;
|
|
93
98
|
- tests, conformance fixtures, coverage output, and secret-scanning helpers;
|
|
94
99
|
Harness-specific Agent Skill installation directories and Skill caches are
|
|
95
100
|
not packaged.
|
|
@@ -4,11 +4,61 @@ This is the current release checklist for the PolyForm Noncommercial-licensed
|
|
|
4
4
|
`@cassiomc1/forgeloop` package. It is a preparation and verification checklist;
|
|
5
5
|
it does not authorize publication.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Current published candidate — 2026-10-14
|
|
8
|
+
|
|
9
|
+
The public core package is `2.0.0-rc.4`; npm `latest` and `next` both resolve
|
|
10
|
+
to RC4. It requires Node.js 24.19.0+ with built-in `node:sqlite`. The MCP
|
|
11
|
+
companion retains restricted distribution. RC4 is a release candidate;
|
|
12
|
+
`soleSQLiteWriter` remains false and stable/production acceptance is open.
|
|
13
|
+
|
|
14
|
+
Observed release evidence:
|
|
15
|
+
|
|
16
|
+
- [RC4 verification and publication](https://github.com/cassiomc1/forgeloop/actions/runs/38629809865)
|
|
17
|
+
passed full release checks and published with provenance.
|
|
18
|
+
- [Exact-version npm audit](https://github.com/cassiomc1/forgeloop/actions/runs/38638055749)
|
|
19
|
+
passed 15/15 checks on each native Windows, macOS and Linux host.
|
|
20
|
+
- [Default latest-install audit](https://github.com/cassiomc1/forgeloop/actions/runs/38669237414)
|
|
21
|
+
installed the unversioned public package with a fresh npm cache,
|
|
22
|
+
configuration, install prefix and project; all 15 checks passed on each OS.
|
|
23
|
+
This run uses the test-harness branch `codex/latest-user-audit-20261014`;
|
|
24
|
+
its `latest` input is not present in the main-branch workflow.
|
|
25
|
+
|
|
26
|
+
The published npm `gitHead` and immutable `v2.0.0-rc.4` tag identify
|
|
27
|
+
`d05c17cddda6a09581d526732b08eb0789b9734f`. The RC3 tag is retained; its
|
|
28
|
+
publication was cancelled before npm publication. RC4 carries those runtime
|
|
29
|
+
fixes plus the Windows archive-repair test correction.
|
|
30
|
+
|
|
31
|
+
Verify the live registry before relying on a channel:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm view @cassiomc1/forgeloop version dist-tags --json
|
|
35
|
+
npm view @cassiomc1/forgeloop@2.0.0-rc.4 gitHead dist.integrity --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Runtime corrections after RC2
|
|
39
|
+
|
|
40
|
+
RC3 and later candidates with runtime changes require fresh full repository
|
|
41
|
+
verification and the clean-user audit on Windows, macOS and Linux. The bounded
|
|
42
|
+
source-equivalence shortcut below applies only to RC1 and RC2. Stable SQLite
|
|
43
|
+
acceptance remains separate; a passing new-user audit does not change
|
|
44
|
+
`soleSQLiteWriter` or authorize a stable 2.x release. Candidates publish to
|
|
45
|
+
`next` under the existing release-channel policy.
|
|
46
|
+
|
|
47
|
+
The `Published npm new-user SQLite audit` workflow supports `candidate` and
|
|
48
|
+
`npm` package sources. Before a PR, test the packed candidate in an isolated
|
|
49
|
+
installation, home directory and project on each platform. After publication,
|
|
50
|
+
dispatch the same workflow with source `npm` and the exact new version. Require
|
|
51
|
+
every check to pass, including live Jev health, concurrent task creation,
|
|
52
|
+
SQLite integrity and a completed-task backup restored to a fresh directory.
|
|
53
|
+
Supply `FORGELOOP_NPM_AUDIT_JEV_KEY` through GitHub Actions secrets and remove
|
|
54
|
+
temporary credentials after the runs finish.
|
|
55
|
+
|
|
56
|
+
## Historical bounded 2.0.0-rc.2 publication
|
|
8
57
|
|
|
9
58
|
The owner approved short verification and an RC fallback on 2026-10-13. This
|
|
10
|
-
candidate is published
|
|
11
|
-
`
|
|
59
|
+
candidate is published to npm `next`. The owner subsequently authorized moving
|
|
60
|
+
`latest` to the existing RC2 through a guarded tag-only promotion. Publishing a
|
|
61
|
+
new stable version remains blocked while `soleSQLiteWriter` is false. A release candidate does not assert completed
|
|
12
62
|
migration acceptance or production readiness.
|
|
13
63
|
|
|
14
64
|
After npm rejected restricted RC1 publication with `E402`, the owner authorized
|
|
@@ -50,9 +100,35 @@ confirmation `PUBLISH PUBLIC PACKAGE`, then verify npm version, Git commit,
|
|
|
50
100
|
tarball integrity, and provenance. Stable publishing retains full checks and
|
|
51
101
|
additionally refuses incomplete sole-writer acceptance.
|
|
52
102
|
|
|
103
|
+
## Owner-authorized default-channel promotion
|
|
104
|
+
|
|
105
|
+
Publishing a new release candidate still uses `next`. Moving an already
|
|
106
|
+
published candidate to `latest` is a separate owner-authorized tag operation;
|
|
107
|
+
it neither republishes the package nor changes the immutable Git tag.
|
|
108
|
+
The owner authorized RC4 after its cross-platform npm audits passed.
|
|
109
|
+
|
|
110
|
+
For that explicitly authorized RC4 operation, authenticated npm CLI access
|
|
111
|
+
was used:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm dist-tag add @cassiomc1/forgeloop@2.0.0-rc.4 latest
|
|
115
|
+
npm view @cassiomc1/forgeloop dist-tags --json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
npm may require separate browser/security-key verification for login and
|
|
119
|
+
for the tag mutation. Credentials belong in npm's authentication flow,
|
|
120
|
+
never documentation, Git or task artifacts. Confirm the registry result
|
|
121
|
+
before reporting success. The version remains an RC; promotion does not
|
|
122
|
+
establish stable SQLite acceptance or production readiness.
|
|
123
|
+
|
|
124
|
+
The older `promote-latest` operation in `npm-publish.yml` and
|
|
125
|
+
`scripts/promote-rc-latest.mjs` is hard-bound to RC2. It is historical tooling,
|
|
126
|
+
not an RC4 promotion path; do not dispatch `POINT LATEST TO RC2` for the
|
|
127
|
+
current release. Its exact-identity and tag guards are retained.
|
|
128
|
+
|
|
53
129
|
## ForgeLoop 2.0.0 candidate scope
|
|
54
130
|
|
|
55
|
-
|
|
131
|
+
A stable 2.0.0 release is not ready while SQLite migration plan §10 has
|
|
56
132
|
open consumer-closure, current-source performance, or platform evidence. Keep
|
|
57
133
|
all acceptance boxes open until their evidence is bound to the exact candidate
|
|
58
134
|
revision.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# ForgeLoop Troubleshooting Guide
|
|
2
2
|
|
|
3
|
+
## Windows Repository Index ZIP extraction
|
|
4
|
+
|
|
5
|
+
When `init` reports `E_REPOSITORY_INDEX_ENGINE_EXTRACTION_FAILED` and
|
|
6
|
+
`tar: This does not look like a tar archive`, a GNU tar from Git may be
|
|
7
|
+
shadowing Windows' ZIP-capable system tool. RC3 selects
|
|
8
|
+
`%SystemRoot%\System32\tar.exe` explicitly for inspection and extraction.
|
|
9
|
+
Archive and binary checksum verification and unsafe-entry rejection still
|
|
10
|
+
apply. A missing system tool remains an explicit extraction failure; ForgeLoop
|
|
11
|
+
does not install replacement software automatically.
|
|
12
|
+
|
|
13
|
+
## Restoring completed tasks into another directory
|
|
14
|
+
|
|
15
|
+
Use an attachment-inclusive `storage-backup` and `storage-restore` with
|
|
16
|
+
externally stopped writers. Historical execution records keep their original
|
|
17
|
+
project root and exact bytes. RC3 accepts them only when they exactly match
|
|
18
|
+
the snapshot bound to the destination's restore journal and storage marker.
|
|
19
|
+
Keep `.forgeloop/storage-restores/<operation-id>/snapshot` and its journal;
|
|
20
|
+
they are retained provenance evidence, not a disposable search cache.
|
|
21
|
+
An altered record, missing snapshot or broken binding remains rejected.
|
|
22
|
+
|
|
23
|
+
Storage restore does not install project code or instruction adapters. Restore
|
|
24
|
+
the project files separately, then run `forgeloop init` in the destination to
|
|
25
|
+
install the kit before expecting the complete `doctor` report to be healthy.
|
|
26
|
+
|
|
27
|
+
## Concurrent task creation
|
|
28
|
+
|
|
29
|
+
Task creation revalidates write claims atomically. If the catalog changes while
|
|
30
|
+
a command is prepared, RC4 refreshes authoritative claim inputs with bounded
|
|
31
|
+
backoff and jitter without replaying the command or its external effects.
|
|
32
|
+
Overlapping claims still fail. Sustained contention can exhaust the retry
|
|
33
|
+
bound and return a catalog-change refusal; inspect fresh task discovery
|
|
34
|
+
before deciding whether to retry.
|
|
35
|
+
|
|
3
36
|
This guide provides symptom-first recovery procedures for common ForgeLoop protocol, state, and verification issues.
|
|
4
37
|
|
|
5
38
|
---
|
|
@@ -100,7 +133,8 @@ tests or change assertions. Local coverage remains an explicit command because
|
|
|
100
133
|
instrumentation adds measurable overhead; the PR unit lane wraps that same
|
|
101
134
|
`test:ci` process across four deterministic shards, then aggregates coverage
|
|
102
135
|
without running the suite again. Docs-only changes run the quick suite only on
|
|
103
|
-
the first Node 24 shard; the other shards
|
|
136
|
+
the first Node 24 shard; the other shards do not install. The minimum Node
|
|
137
|
+
24.19.0 compatibility lane runs only for applicable source or runtime changes.
|
|
104
138
|
|
|
105
139
|
#### Native Windows guidance
|
|
106
140
|
|
|
@@ -1405,6 +1405,17 @@
|
|
|
1405
1405
|
"self-hosted-node-validation"
|
|
1406
1406
|
],
|
|
1407
1407
|
"packaged": true
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
"path": "docs/FORGELOOP_AUDIT.md",
|
|
1411
|
+
"id": "forgeloop-audit",
|
|
1412
|
+
"class": "operational",
|
|
1413
|
+
"audience": [
|
|
1414
|
+
"user",
|
|
1415
|
+
"maintainer"
|
|
1416
|
+
],
|
|
1417
|
+
"canonicalFor": [],
|
|
1418
|
+
"packaged": true
|
|
1408
1419
|
}
|
|
1409
1420
|
]
|
|
1410
1421
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cassiomc1/forgeloop",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.5",
|
|
4
4
|
"description": "Evidence-first engineering protocol for AI coding environments and developer workflows",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"docs/MCP.md",
|
|
83
83
|
"docs/UNIVERSAL_INTEGRATION.md",
|
|
84
84
|
"docs/AUDIT_UX.md",
|
|
85
|
+
"docs/FORGELOOP_AUDIT.md",
|
|
85
86
|
"docs/DIAGNOSTIC_MODEL.md",
|
|
86
87
|
"docs/EXECUTION_TRACE.md",
|
|
87
88
|
"docs/CODE_ATTESTATION.md",
|
|
@@ -193,7 +194,7 @@
|
|
|
193
194
|
},
|
|
194
195
|
"dependencies": {
|
|
195
196
|
"@typesafe-ai/sdk": "0.6.0",
|
|
196
|
-
"smol-toml": "1.
|
|
197
|
+
"smol-toml": "1.9.0"
|
|
197
198
|
},
|
|
198
199
|
"exports": {
|
|
199
200
|
".": "./src/cli.js",
|
|
@@ -42,6 +42,12 @@ Runtime registries and validators outrank canonical protocol docs; canonical doc
|
|
|
42
42
|
|
|
43
43
|
In ForgeLoop's own source repository, use \`node src/cli.js\`. In a consumer repository, use that repository's configured local invocation. Never silently install or upgrade ForgeLoop, and never use a stale global CLI when repository-local code exists. If compatibility is uncertain, inspect \`protocol-info --json\`.
|
|
44
44
|
|
|
45
|
+
## ForgeLoopAudit Workspace
|
|
46
|
+
|
|
47
|
+
ForgeLoopAudit is maintained at \`ForgeLoopAudit/\` inside the ForgeLoop repository, replacing the standalone repository and sibling path \`../ForgeLoopAudit\`. ForgeLoop is its source and contribution home. This source layout is not a prerequisite for consumer projects or npm installations. Audit remains a read-only auditor with its own package, documentation, build, and verification commands; it is not included in the core ForgeLoop npm package.
|
|
48
|
+
|
|
49
|
+
Resolve the target root before running commands. For core work, stay at the ForgeLoop root and use \`node src/cli.js\`. For Audit work, enter \`ForgeLoopAudit/\`, read its own project instructions and \`README.md\`, and resolve its configured ForgeLoop runtime there. Audit development uses \`npm run dev:web\`; its full verification uses \`npm run verify:full\`, both from the Audit root. Do not apply the parent's CLI path blindly in the child directory or merge their task state. A directory move does not rebind receipts, workspace bindings, or stored project paths; inspect affected artifacts through supported tooling and never rewrite protocol state to repair a moved root.
|
|
50
|
+
|
|
45
51
|
## Operating Workflow
|
|
46
52
|
|
|
47
53
|
1. Discover existing work with \`node src/cli.js task-list --json\`.
|
|
@@ -27,6 +27,12 @@ Runtime registries and validators outrank canonical protocol docs; canonical doc
|
|
|
27
27
|
|
|
28
28
|
In ForgeLoop's own source repository, use `node src/cli.js`. In a consumer repository, use that repository's configured local invocation. Never silently install or upgrade ForgeLoop, and never use a stale global CLI when repository-local code exists. If compatibility is uncertain, inspect `protocol-info --json`.
|
|
29
29
|
|
|
30
|
+
## ForgeLoopAudit Workspace
|
|
31
|
+
|
|
32
|
+
ForgeLoopAudit is maintained at `ForgeLoopAudit/` inside the ForgeLoop repository, replacing the standalone repository and sibling path `../ForgeLoopAudit`. ForgeLoop is its source and contribution home. This source layout is not a prerequisite for consumer projects or npm installations. Audit remains a read-only auditor with its own package, documentation, build, and verification commands; it is not included in the core ForgeLoop npm package.
|
|
33
|
+
|
|
34
|
+
Resolve the target root before running commands. For core work, stay at the ForgeLoop root and use `node src/cli.js`. For Audit work, enter `ForgeLoopAudit/`, read its own project instructions and `README.md`, and resolve its configured ForgeLoop runtime there. Audit development uses `npm run dev:web`; its full verification uses `npm run verify:full`, both from the Audit root. Do not apply the parent's CLI path blindly in the child directory or merge their task state. A directory move does not rebind receipts, workspace bindings, or stored project paths; inspect affected artifacts through supported tooling and never rewrite protocol state to repair a moved root.
|
|
35
|
+
|
|
30
36
|
## Operating Workflow
|
|
31
37
|
|
|
32
38
|
1. Discover existing work with `node src/cli.js task-list --json`.
|
|
@@ -124,6 +124,19 @@ function commandProvenanceError(message = "Observed command evidence requires a
|
|
|
124
124
|
return artifactError("E_COMMAND_PROVENANCE_UNATTESTED", message, [ARTIFACT_PATHS.receipt]);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
async function validateExecutionProjectRoot({ target, taskId, check, execution, protocolProjectRoot, allowForeignCwd, executionArtifacts }) {
|
|
128
|
+
if (allowForeignCwd || path.resolve(protocolProjectRoot) === path.resolve(target)) return;
|
|
129
|
+
const { isRetainedRestoreExecution } = await import("../storage/restore-execution-provenance.js");
|
|
130
|
+
const restored = !executionArtifacts && await isRetainedRestoreExecution(target, execution, { taskId });
|
|
131
|
+
if (!restored) {
|
|
132
|
+
throw artifactError(
|
|
133
|
+
"E_EXECUTION_REF_INVALID",
|
|
134
|
+
"Execution artifact protocol project root does not match the current target",
|
|
135
|
+
[taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
/**
|
|
128
141
|
* Revalidate the ForgeLoop-owned execution artifact behind an observed command
|
|
129
142
|
* check. This is intentionally asynchronous so completion and audit can verify
|
|
@@ -194,13 +207,7 @@ export async function validateCheckExecutionProvenance(check, {
|
|
|
194
207
|
[taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
|
|
195
208
|
);
|
|
196
209
|
}
|
|
197
|
-
|
|
198
|
-
throw artifactError(
|
|
199
|
-
"E_EXECUTION_REF_INVALID",
|
|
200
|
-
"Execution artifact protocol project root does not match the current target",
|
|
201
|
-
[taskId ? taskExecutionPath(taskId, check.executionRef) : ARTIFACT_PATHS.executionDirectory],
|
|
202
|
-
);
|
|
203
|
-
}
|
|
210
|
+
await validateExecutionProjectRoot({ target, taskId, check, execution, protocolProjectRoot, allowForeignCwd, executionArtifacts });
|
|
204
211
|
if (check.status === "passed" && (execution.status !== "passed" || execution.exitCode !== 0)) {
|
|
205
212
|
throw artifactError(
|
|
206
213
|
"E_EXECUTION_REF_INVALID",
|
|
@@ -181,8 +181,14 @@ export function validateArchiveEntry(entry) {
|
|
|
181
181
|
return normalized;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
export function resolveArchiveTool(archive, { platform = process.platform, env = process.env } = {}) {
|
|
185
|
+
// Windows ships a ZIP-capable bsdtar. Git's GNU tar can shadow it on PATH.
|
|
186
|
+
if (platform === "win32") return path.win32.join(env.SystemRoot || "C:\\Windows", "System32", "tar.exe");
|
|
187
|
+
return archive === "zip" ? "unzip" : "tar";
|
|
188
|
+
}
|
|
189
|
+
|
|
184
190
|
async function listArchiveEntries(archivePath, { archive = "tar.gz", execFileImpl = nodeExecFile } = {}) {
|
|
185
|
-
const listCommand = archive
|
|
191
|
+
const listCommand = resolveArchiveTool(archive);
|
|
186
192
|
const archiveName = path.basename(archivePath);
|
|
187
193
|
const listArgs = listCommand === "unzip" ? ["-Z1", archiveName] : ["-tf", archiveName];
|
|
188
194
|
try {
|
|
@@ -195,7 +201,7 @@ async function listArchiveEntries(archivePath, { archive = "tar.gz", execFileImp
|
|
|
195
201
|
|
|
196
202
|
async function extractArchive(archivePath, extractionDirectory, { archive = "tar.gz", execFileImpl = nodeExecFile } = {}) {
|
|
197
203
|
await listArchiveEntries(archivePath, { archive, execFileImpl });
|
|
198
|
-
const extractCommand = archive
|
|
204
|
+
const extractCommand = resolveArchiveTool(archive);
|
|
199
205
|
const destination = path.relative(path.dirname(archivePath), extractionDirectory).replaceAll("\\", "/") || ".";
|
|
200
206
|
const extractArgs = extractCommand === "unzip"
|
|
201
207
|
? ["-q", path.basename(archivePath), "-d", destination]
|
|
@@ -26,7 +26,7 @@ function artifactSchema(row) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Validate imported payloads with their existing protocol schemas and rules. */
|
|
29
|
-
export async function validateMigrationDatabase(db, { target, packageRoot }) {
|
|
29
|
+
export async function validateMigrationDatabase(db, { target, packageRoot, archivedExecutions = false }) {
|
|
30
30
|
const schemas = new Map();
|
|
31
31
|
const validate = async (text, name, label, taskId = null) => {
|
|
32
32
|
if (!name) throw Object.assign(new Error(`Unmapped operational artifact: ${label}`), { code: "E_STORAGE_MIGRATION_UNMAPPED_SOURCE" });
|
|
@@ -55,13 +55,13 @@ export async function validateMigrationDatabase(db, { target, packageRoot }) {
|
|
|
55
55
|
validator?.(value);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
return withOperationalStore({ db, target }, async () => {
|
|
58
|
+
return withOperationalStore({ db, target, readOnly: true }, async () => {
|
|
59
59
|
const tasks = [];
|
|
60
60
|
for (const row of db.prepare("SELECT task_id FROM tasks ORDER BY task_id").iterate()) {
|
|
61
61
|
const state = await readWorkState(target, { taskId: row.task_id, packageRoot });
|
|
62
62
|
for (const check of state?.checks ?? []) {
|
|
63
63
|
if (check.provenance === "FORGELOOP_EXECUTED") {
|
|
64
|
-
await validateCheckExecutionProvenance(check, { target, packageRoot, taskId: row.task_id });
|
|
64
|
+
await validateCheckExecutionProvenance(check, { target, packageRoot, taskId: row.task_id, allowForeignCwd: archivedExecutions });
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
const ledger = await validateEventLedger(target, packageRoot, { taskId: row.task_id });
|
|
@@ -77,7 +77,7 @@ export async function resumeProjectReplacementPreparation(target, { operationId,
|
|
|
77
77
|
if (intent.phase === "PREPARING") {
|
|
78
78
|
await ensurePreparedRestoreSnapshot(target, current.root, intent, { journalFilename: "replacement-intent.json" });
|
|
79
79
|
await withVerifiedProjectStorageBackup(await assertSafePath(current.root, "snapshot"), async retained => {
|
|
80
|
-
await validateMigrationDatabase(retained.db, { target: retained.root, packageRoot });
|
|
80
|
+
await validateMigrationDatabase(retained.db, { target: retained.root, packageRoot, archivedExecutions: true });
|
|
81
81
|
await withVerifiedProjectStorageBackup(intent.source, source => {
|
|
82
82
|
if (canonicalFingerprint(source.manifest) !== intent.sourceFingerprint
|
|
83
83
|
|| canonicalFingerprint(logicalSnapshot(source.db)) !== canonicalFingerprint(logicalSnapshot(retained.db))) throw invalid("Replacement recovery snapshot differs from recorded source");
|
|
@@ -99,7 +99,7 @@ export async function resumeProjectReplacementPreparation(target, { operationId,
|
|
|
99
99
|
export async function replaceActiveProjectStorage(source, target, { writersQuiesced = false, packageRoot = getPackageRoot() } = {}) {
|
|
100
100
|
if (writersQuiesced !== true) throw Object.assign(new Error("Exclude writers and close their database connections before active replacement"), { code: "E_STORAGE_MIGRATION_QUIESCENCE_REQUIRED" });
|
|
101
101
|
return withVerifiedProjectStorageBackup(source, async ({ db, root, manifest }) => {
|
|
102
|
-
await validateMigrationDatabase(db, { target: root, packageRoot });
|
|
102
|
+
await validateMigrationDatabase(db, { target: root, packageRoot, archivedExecutions: true });
|
|
103
103
|
const relative = path.relative(path.resolve(target), root).split(path.sep).join("/");
|
|
104
104
|
if (REPLACEMENT_ACTIVE_ROOTS.some(name => relative === name || relative.startsWith(`${name}/`))) throw invalid("Replacement source cannot be archived outgoing operational state");
|
|
105
105
|
return withStorageMaintenance(target, async () => {
|
|
@@ -119,7 +119,7 @@ export async function replaceActiveProjectStorage(source, target, { writersQuies
|
|
|
119
119
|
const snapshot = await assertSafePath(operationRoot, "snapshot");
|
|
120
120
|
await backupProjectStorage(db, root, snapshot);
|
|
121
121
|
await withVerifiedProjectStorageBackup(snapshot, async retained => {
|
|
122
|
-
await validateMigrationDatabase(retained.db, { target: retained.root, packageRoot });
|
|
122
|
+
await validateMigrationDatabase(retained.db, { target: retained.root, packageRoot, archivedExecutions: true });
|
|
123
123
|
await withVerifiedProjectStorageBackup(root, current => {
|
|
124
124
|
if (canonicalFingerprint(current.manifest) !== intent.sourceFingerprint
|
|
125
125
|
|| canonicalFingerprint(logicalSnapshot(current.db)) !== canonicalFingerprint(logicalSnapshot(retained.db))) throw invalid("Incoming source changed during replacement preparation");
|
|
@@ -92,7 +92,7 @@ export async function restoreProjectStorageToFreshProject(source, target, { writ
|
|
|
92
92
|
if (canonicalFingerprint(current.manifest) !== canonicalFingerprint(sourceManifest)
|
|
93
93
|
|| canonicalFingerprint(logicalSnapshot(current.db)) !== canonicalFingerprint(logicalSnapshot(snapshot))) throw invalid("Source backup changed during restore preparation");
|
|
94
94
|
});
|
|
95
|
-
await validateMigrationDatabase(snapshot, { target: retained, packageRoot });
|
|
95
|
+
await validateMigrationDatabase(snapshot, { target: retained, packageRoot, archivedExecutions: true });
|
|
96
96
|
const binding = { operationId, databaseSchemaVersion: readStorageMeta(snapshot).schema_version,
|
|
97
97
|
sourceInventoryFingerprint: canonicalFingerprint({ database: manifest.databaseSha256, inventory: manifest.inventorySha256, references: manifest.references }) };
|
|
98
98
|
Object.assign(journal, { phase: "READY", binding, snapshotFingerprint: canonicalFingerprint(manifest) });
|
|
@@ -111,7 +111,7 @@ async function publishReadyRestore(target, operationRoot, journal, { packageRoot
|
|
|
111
111
|
const persist = () => writeFileAtomic(journalPath, `${JSON.stringify(journal, null, 2)}\n`);
|
|
112
112
|
const { operationId, binding } = journal;
|
|
113
113
|
return withVerifiedProjectStorageBackup(retained, async ({ db: snapshot, manifest }) => {
|
|
114
|
-
await validateMigrationDatabase(snapshot, { target: retained, packageRoot });
|
|
114
|
+
await validateMigrationDatabase(snapshot, { target: retained, packageRoot, archivedExecutions: true });
|
|
115
115
|
await assertOwnedStorageMaintenance(target);
|
|
116
116
|
await verifyProjectRestoreJournal(target, operationId, { expectedOwnerId: await assertOwnedStorageMaintenance(target) });
|
|
117
117
|
await assertRestorePublicationLayout(target, snapshot);
|
|
@@ -186,7 +186,7 @@ async function validatePreparedRestore(target, root, journal, packageRoot) {
|
|
|
186
186
|
await withVerifiedProjectStorageBackup(journal.source, async source => {
|
|
187
187
|
if (canonicalFingerprint(source.manifest) !== journal.sourceFingerprint
|
|
188
188
|
|| canonicalFingerprint(logicalSnapshot(source.db)) !== canonicalFingerprint(logicalSnapshot(snapshot))) throw invalid("Prepared restore differs from its recorded source backup");
|
|
189
|
-
await validateMigrationDatabase(snapshot, { target: snapshotPath, packageRoot });
|
|
189
|
+
await validateMigrationDatabase(snapshot, { target: snapshotPath, packageRoot, archivedExecutions: true });
|
|
190
190
|
});
|
|
191
191
|
await assertOwnedStorageMaintenance(target);
|
|
192
192
|
Object.assign(journal, { phase: "READY", snapshotFingerprint: canonicalFingerprint(manifest), binding: {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { canonicalFingerprint } from "../core/artifacts.js";
|
|
2
|
+
import { readStorageVersionMarker } from "./storage-marker.js";
|
|
3
|
+
import { verifyProjectRestoreJournal } from "./restore-journal.js";
|
|
4
|
+
import { withVerifiedProjectStorageBackup } from "./project-backup.js";
|
|
5
|
+
import { operationalContext } from "./operational-context.js";
|
|
6
|
+
|
|
7
|
+
/** Historical roots remain valid only for exact records in the bound snapshot. */
|
|
8
|
+
export async function isRetainedRestoreExecution(target, execution, { taskId } = {}) {
|
|
9
|
+
// Replacement journals also audit incoming archives. Those read-only audits
|
|
10
|
+
// must not select their snapshot as the caller's live operational project.
|
|
11
|
+
return operationalContext.run(null, () => verifyRetainedExecution(target, execution, taskId));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function verifyRetainedExecution(target, execution, taskId) {
|
|
15
|
+
const marker = await readStorageVersionMarker(target);
|
|
16
|
+
if (!marker?.operationId) return false;
|
|
17
|
+
let retained;
|
|
18
|
+
try { retained = await verifyProjectRestoreJournal(target, marker.operationId); }
|
|
19
|
+
catch (error) { if (error.code === "ENOENT") return false; throw error; }
|
|
20
|
+
if (!retained.snapshotVerified || !["PUBLISHING", "ACTIVE"].includes(retained.journal.phase)) return false;
|
|
21
|
+
return withVerifiedProjectStorageBackup(retained.snapshot, ({ db }) => {
|
|
22
|
+
const row = db.prepare("SELECT payload_json FROM executions WHERE task_id = ? AND execution_id = ?")
|
|
23
|
+
.get(taskId ?? execution.taskId, execution.executionId);
|
|
24
|
+
return Boolean(row && canonicalFingerprint(JSON.parse(row.payload_json)) === canonicalFingerprint(execution));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -22,7 +22,7 @@ const belongs = (name, root) => name === root || name.startsWith(`${root}/`);
|
|
|
22
22
|
async function incomingSnapshot(target, operationId, packageRoot) {
|
|
23
23
|
const root = await assertSafePath(target, `.forgeloop/storage-restores/${operationId}/snapshot`);
|
|
24
24
|
return withVerifiedProjectStorageBackup(root, async ({ db, manifest }) => {
|
|
25
|
-
await validateMigrationDatabase(db, { target: root, packageRoot });
|
|
25
|
+
await validateMigrationDatabase(db, { target: root, packageRoot, archivedExecutions: true });
|
|
26
26
|
return { root, fingerprint: canonicalFingerprint(manifest) };
|
|
27
27
|
});
|
|
28
28
|
}
|
|
@@ -80,7 +80,7 @@ export async function prepareActiveStorageReplacement(target, operationId, { wri
|
|
|
80
80
|
const backup = await assertSafePath(root, "backup");
|
|
81
81
|
await backupProjectStorage(db, target, backup);
|
|
82
82
|
await withVerifiedProjectStorageBackup(backup, async retained => {
|
|
83
|
-
await validateMigrationDatabase(retained.db, { target: retained.root, packageRoot });
|
|
83
|
+
await validateMigrationDatabase(retained.db, { target: retained.root, packageRoot, archivedExecutions: true });
|
|
84
84
|
const logicalFingerprint = canonicalFingerprint(logicalSnapshot(retained.db));
|
|
85
85
|
if (logicalFingerprint !== canonicalFingerprint(logicalSnapshot(db))) throw invalid("Outgoing state changed while its retained backup was prepared");
|
|
86
86
|
Object.assign(manifest, { backupFingerprint: canonicalFingerprint(retained.manifest), logicalFingerprint });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { publishAttachmentFile, verifyAttachmentFile } from "./attachment-files.js";
|
|
2
2
|
import { insertVerifiedAttachmentReference, validateAttachmentReference } from "./attachment-references.js";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { randomUUID, createHash } from "node:crypto";
|
|
4
|
+
import { randomUUID, createHash, randomInt } from "node:crypto";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
6
|
import { operationalContext, getOperationalStore } from "./operational-context.js";
|
|
6
7
|
import { canonicalFingerprint } from "../core/artifacts.js";
|
|
7
8
|
import { taskStorageKey } from "../core/task-identity.js";
|
|
@@ -547,7 +548,10 @@ async function commitPreparedStore(store, packageRoot) {
|
|
|
547
548
|
for (let refresh = 0; ; refresh += 1) {
|
|
548
549
|
try { store.commit(); return; }
|
|
549
550
|
catch (error) {
|
|
550
|
-
if (error.code !== "E_STORAGE_CATALOG_CHANGED" || refresh >=
|
|
551
|
+
if (error.code !== "E_STORAGE_CATALOG_CHANGED" || refresh >= 16) throw error;
|
|
552
|
+
// Spread simultaneous creators before refreshing their authoritative
|
|
553
|
+
// claim inputs. The prepared command and external effects are not replayed.
|
|
554
|
+
await delay(Math.min(5 * (2 ** refresh), 100) + randomInt(0, 20));
|
|
551
555
|
store.reads.delete("task-catalog");
|
|
552
556
|
const { discoverTasks } = await import("../core/task-discovery.js");
|
|
553
557
|
const { assertNoScopeConflicts } = await import("../core/task-scope.js");
|