owlrunkit 0.14.0 → 0.15.0
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/CHANGELOG.md +27 -0
- package/README.md +46 -2
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +76 -14
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +1 -1
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +5 -0
- package/packages/attest/src/quick.mjs +5 -0
- package/registry-release-policy-v1.json +1 -1
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/core-contract.mjs +140 -4
- package/scripts/runkit-contract/install-codex-skill.mjs +505 -11
- package/scripts/runkit-contract/lifecycle-orchestration.mjs +119 -4
- package/scripts/runkit-contract/onboarding-doctor.mjs +205 -7
- package/scripts/runkit-contract/profile-onboarding.mjs +188 -10
- package/scripts/runkit-contract/registry-adoption-gate.mjs +111 -0
- package/scripts/runkit-contract/registry-adoption.mjs +2 -1
- package/scripts/runkit-contract/runkit-cli.mjs +65 -23
- package/scripts/runkit-contract/schemas/verification-finalize-attempt-v1.schema.json +52 -0
- package/scripts/runkit-contract/schemas/verification-preflight-v1.schema.json +52 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
- Makes detected npm launchers resolve to absolute regular files that Formal
|
|
6
|
+
verification can execute without weakening symlink rejection.
|
|
7
|
+
- Adds workspace-aware `apps/*` and `packages/*` profile candidates with exact
|
|
8
|
+
workspace script commands.
|
|
9
|
+
- Expands read-only runtime-isolation diagnostics across common formatters,
|
|
10
|
+
linters, and explicitly broad test collectors.
|
|
11
|
+
- Preserves rejected verification preflights as append-only execution evidence
|
|
12
|
+
and persists non-accepted finalize outcomes with their request binding.
|
|
13
|
+
- Consumes completed adoption/profile facts in doctor and inspect guidance only
|
|
14
|
+
when the launcher and exact local registry bindings are still safe.
|
|
15
|
+
- Keeps Contract v0.2, 0.13/0.14 configurations, profiles, historical
|
|
16
|
+
executions, receipts, and verifier Core identities readable; repeated
|
|
17
|
+
upgrades write exclusive sequenced ConfigV2 Core-refresh receipts.
|
|
18
|
+
- Refuses Core or process-wide Codex Skill upgrades before mutation when any
|
|
19
|
+
explicitly enrolled project has an active execution, writer lease,
|
|
20
|
+
ambiguous closeout, or invalid config.
|
|
21
|
+
- Archives the exact prior Skill and project config bytes, adds receipt-bound
|
|
22
|
+
exact rollback, and adds an exact-Core active-fleet recovery path for
|
|
23
|
+
installations already drifted by an older unsafe replacement.
|
|
24
|
+
- Runs registry pack/install/upgrade/rollback gates through npm owned by each
|
|
25
|
+
selected Node runtime and proves active work blocks upgrade before mutation
|
|
26
|
+
and rollback restores the exact prior Skill identity and config bytes.
|
|
27
|
+
- Keeps OwlCoda CLI `0.15.31` unchanged and grants no Git, publish, release, or
|
|
28
|
+
deploy authority.
|
|
29
|
+
|
|
3
30
|
## 0.14.0
|
|
4
31
|
|
|
5
32
|
- Adds ordinary `--help` and `--version`, read-only `doctor`, exact official
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ release authority.
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install --save-exact owlrunkit@0.
|
|
14
|
+
npm install --save-exact owlrunkit@0.15.0
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Check the ordinary CLI surface, then initialize a project:
|
|
@@ -22,7 +22,7 @@ owlrunkit --version
|
|
|
22
22
|
owlrunkit init --workspace /absolute/path/to/project
|
|
23
23
|
owlrunkit doctor --workspace /absolute/path/to/project
|
|
24
24
|
owlrunkit profiles detect --workspace /absolute/path/to/project --dry-run
|
|
25
|
-
owlrunkit adopt --workspace /absolute/path/to/project --exact owlrunkit@0.
|
|
25
|
+
owlrunkit adopt --workspace /absolute/path/to/project --exact owlrunkit@0.15.0
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
`doctor` is read-only and distinguishes offline/DNS timeout, registry
|
|
@@ -32,6 +32,50 @@ detection is deterministic and dry-run only; use `profiles validate` and
|
|
|
32
32
|
evidence only after the exact installed package and lockfile match the official
|
|
33
33
|
npm registry release.
|
|
34
34
|
|
|
35
|
+
Core 0.15 keeps Contract v0.2 and all 0.13/0.14 project artifact schemas
|
|
36
|
+
readable. `init` refreshes an older ConfigV2 Core binding with an append-only
|
|
37
|
+
migration receipt only when the project has no active execution, active writer
|
|
38
|
+
lease, or ambiguous control state. Otherwise the upgrade exits before changing
|
|
39
|
+
the config or writing migration evidence. Legal refreshes preserve profiles,
|
|
40
|
+
executions, receipts, and adoption evidence byte-for-byte. Existing profiles
|
|
41
|
+
are never rewritten automatically; a fresh `profiles detect --dry-run` supplies
|
|
42
|
+
safer, workspace-aware candidates for human review.
|
|
43
|
+
Repeated Core refreshes create sequenced receipts without replacing an earlier
|
|
44
|
+
migration event, and rejected Formal finalize gates retain their request-bound
|
|
45
|
+
reason as append-only evidence.
|
|
46
|
+
|
|
47
|
+
## Updating the process-wide Codex Skill
|
|
48
|
+
|
|
49
|
+
Do not replace a shared installed Skill while any consuming project is active.
|
|
50
|
+
For a changed Skill manifest, enumerate the complete consuming fleet with
|
|
51
|
+
repeated `--workspace` arguments:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
node ./scripts/runkit-contract/install-codex-skill.mjs install \
|
|
55
|
+
--repository /absolute/path/to/owlrunkit-package \
|
|
56
|
+
--target /absolute/path/to/installed/owlcoda-runkit \
|
|
57
|
+
--archive /absolute/path/to/skill-archives \
|
|
58
|
+
--workspace /absolute/project-a \
|
|
59
|
+
--workspace /absolute/project-b
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The update is refused before global or project mutation if any listed project
|
|
63
|
+
is active or invalid. A successful update records the exact prior Skill
|
|
64
|
+
identity and each original project config in an append-only receipt. Restore
|
|
65
|
+
that exact state with:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
node ./scripts/runkit-contract/install-codex-skill.mjs restore \
|
|
69
|
+
--target /absolute/path/to/installed/owlcoda-runkit \
|
|
70
|
+
--archive /absolute/path/to/skill-archives \
|
|
71
|
+
--receipt /absolute/path/to/skill-upgrade-receipt.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`recover-active` is reserved for a fleet already drifted by an older unsafe
|
|
75
|
+
replacement. It accepts only an archived Skill whose Core identity exactly
|
|
76
|
+
matches every affected config and active execution pin, leaves all project
|
|
77
|
+
bytes untouched, and writes a recovery receipt.
|
|
78
|
+
|
|
35
79
|
Use Quick Verification for one low-risk source-bound command that needs a
|
|
36
80
|
reusable receipt but no writer lease or Formal acceptance. Use Formal Delivery
|
|
37
81
|
for multi-writer, long-running, migration, integration, release, production,
|
|
@@ -7,8 +7,8 @@ description: Run receipt-backed, project-owned software execution with OwlCoda R
|
|
|
7
7
|
|
|
8
8
|
Use the bundled deterministic Core to coordinate work through project artifacts rather than chat memory. Require Node.js 20 or later.
|
|
9
9
|
|
|
10
|
-
Current release: standalone `owlrunkit@0.
|
|
11
|
-
Core `0.
|
|
10
|
+
Current release: standalone `owlrunkit@0.15.0`, bundling Contract `0.2` and
|
|
11
|
+
Core `0.15.0`. The root `owlcoda` package has a separate version lifecycle.
|
|
12
12
|
|
|
13
13
|
The session is an execution interface. Project artifacts are the system of record.
|
|
14
14
|
|
|
@@ -111,17 +111,55 @@ node "$RUNKIT_PACKAGE_ROOT/scripts/runkit-contract/install-codex-skill.mjs" \
|
|
|
111
111
|
|
|
112
112
|
node "$RUNKIT_PACKAGE_ROOT/scripts/runkit-contract/install-codex-skill.mjs" \
|
|
113
113
|
install --repository "$RUNKIT_PACKAGE_ROOT" \
|
|
114
|
-
--target "$RUNKIT_SKILL_TARGET" --archive "$RUNKIT_SKILL_ARCHIVE_ROOT"
|
|
114
|
+
--target "$RUNKIT_SKILL_TARGET" --archive "$RUNKIT_SKILL_ARCHIVE_ROOT" \
|
|
115
|
+
--workspace /absolute/project-a --workspace /absolute/project-b
|
|
115
116
|
```
|
|
116
117
|
|
|
117
118
|
The install manifest binds every Skill, Core, contract and attest file.
|
|
118
|
-
Unknown installed drift fails closed instead of being overwritten.
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
Unknown installed drift fails closed instead of being overwritten. A changed
|
|
120
|
+
process-wide Skill requires an explicit fleet: pass every project that consumes
|
|
121
|
+
that Skill as a repeated `--workspace`. The installer validates every project
|
|
122
|
+
config and refuses the whole update before replacing the Skill or changing any
|
|
123
|
+
project when one project has an active execution, an active writer lease,
|
|
124
|
+
ambiguous control state, or invalid configuration. Omitting the fleet is an
|
|
125
|
+
error; do not pass a partial fleet to bypass this guard.
|
|
126
|
+
|
|
127
|
+
A successful update archives the exact prior Skill, migrates each inactive
|
|
128
|
+
project, and writes an append-only upgrade receipt containing the prior Skill
|
|
129
|
+
manifest identity and each prior ConfigV2 byte sequence. Use that receipt for
|
|
130
|
+
an exact rollback:
|
|
121
131
|
|
|
122
|
-
|
|
132
|
+
```bash
|
|
133
|
+
node "$RUNKIT_PACKAGE_ROOT/scripts/runkit-contract/install-codex-skill.mjs" \
|
|
134
|
+
restore --target "$RUNKIT_SKILL_TARGET" \
|
|
135
|
+
--archive "$RUNKIT_SKILL_ARCHIVE_ROOT" \
|
|
136
|
+
--receipt /absolute/path/to/skill-upgrade-receipt.json
|
|
137
|
+
```
|
|
123
138
|
|
|
124
|
-
|
|
139
|
+
If an older unsafe process-wide replacement already drifted a running fleet,
|
|
140
|
+
do not run ordinary `install` or `restore`. Recover only an archived Skill whose
|
|
141
|
+
embedded Core identity exactly matches every affected project config and active
|
|
142
|
+
execution pin:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
node "$RUNKIT_PACKAGE_ROOT/scripts/runkit-contract/install-codex-skill.mjs" \
|
|
146
|
+
recover-active --target "$RUNKIT_SKILL_TARGET" \
|
|
147
|
+
--archive "$RUNKIT_SKILL_ARCHIVE_ROOT" \
|
|
148
|
+
--archived-installation /absolute/path/to/exact-archived-skill \
|
|
149
|
+
--workspace /absolute/affected-project
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Active recovery changes only the process-wide Skill installation. It preserves
|
|
153
|
+
project config, execution, lease, receipt, and closeout bytes and writes an
|
|
154
|
+
append-only recovery receipt. It refuses an approximate version match or a
|
|
155
|
+
fleet with no matching active execution. An accepted receipt, ready-for-commit
|
|
156
|
+
receipt, package publication, valid install, rollback, or recovery still does
|
|
157
|
+
not grant Git, npm, release, deploy, or foreign-project authority.
|
|
158
|
+
|
|
159
|
+
## Core v0.15 consolidated onboarding and compatibility
|
|
160
|
+
|
|
161
|
+
Core v0.14 introduced self-service onboarding. The bundled Core is now
|
|
162
|
+
`0.15.0`; new users should not need internal module calls,
|
|
125
163
|
an external schema validator, or the full Formal lifecycle merely to understand
|
|
126
164
|
whether a project can adopt RunKit.
|
|
127
165
|
|
|
@@ -132,7 +170,7 @@ owlrunkit doctor --workspace "$PROJECT_ROOT"
|
|
|
132
170
|
owlrunkit profiles detect --workspace "$PROJECT_ROOT" --dry-run
|
|
133
171
|
owlrunkit profiles validate --workspace "$PROJECT_ROOT"
|
|
134
172
|
owlrunkit profiles impact --workspace "$PROJECT_ROOT" --changed path/to/file
|
|
135
|
-
owlrunkit adopt --workspace "$PROJECT_ROOT" --exact owlrunkit@0.
|
|
173
|
+
owlrunkit adopt --workspace "$PROJECT_ROOT" --exact owlrunkit@0.15.0
|
|
136
174
|
```
|
|
137
175
|
|
|
138
176
|
`doctor` and `profiles detect --dry-run` are read-only. Registry diagnostics use
|
|
@@ -141,16 +179,40 @@ failures, missing versions and local-install binding mismatches without
|
|
|
141
179
|
printing credentials. Profile detection is deterministic evidence for a human
|
|
142
180
|
decision; it does not write or overwrite project configuration.
|
|
143
181
|
|
|
144
|
-
`adopt` accepts only an exact `owlrunkit@0.
|
|
182
|
+
`adopt` accepts only an exact `owlrunkit@0.15.0` binding from the official npm
|
|
145
183
|
registry. It recomputes the registry and local install/lock facts, invokes the
|
|
146
184
|
strict adoption evaluator, and writes typed project evidence only after an
|
|
147
185
|
eligible result. Worktrees, `file:` dependencies, local tarballs, Git sources,
|
|
148
186
|
workspace links and mutable tags remain ineligible.
|
|
149
187
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
188
|
+
Detected commands use canonical absolute regular-file launchers, including
|
|
189
|
+
NVM-managed npm installs, so a detector result does not conflict with Formal
|
|
190
|
+
symlink rejection. Workspace discovery understands safe literal and one-level
|
|
191
|
+
`apps/*` / `packages/*` patterns and emits exact root-owned npm workspace
|
|
192
|
+
commands. Detection remains dry-run only and never rewrites an existing
|
|
193
|
+
profile.
|
|
194
|
+
|
|
195
|
+
`doctor` warns when explicit broad formatter, linter, or test-collector
|
|
196
|
+
commands can include `.owlcoda/runkit/`; it never edits ignore or tool
|
|
197
|
+
configuration. `init` and `inspect` consume valid current-version adoption and
|
|
198
|
+
profile facts only while the exact local registry binding remains intact and
|
|
199
|
+
every launcher is Formal-safe; otherwise launcher review or adoption remains
|
|
200
|
+
explicitly incomplete. The recovery action remains authoritative. A rejected
|
|
201
|
+
Formal executable preflight or non-accepted finalize gate is preserved under
|
|
202
|
+
the execution before the CLI returns the result.
|
|
203
|
+
|
|
204
|
+
Contract v0.2 remains unchanged. Upgrading a 0.13 or 0.14 ConfigV2 project
|
|
205
|
+
is allowed only while its RunKit control state is inactive and unambiguous. An
|
|
206
|
+
active execution or writer lease refuses the upgrade before `config.json` or
|
|
207
|
+
migration evidence changes. A legal refresh writes an exclusive sequenced
|
|
208
|
+
Core-refresh migration receipt and changes only `config.json`; a repeated
|
|
209
|
+
transition cannot replace its first receipt. Compatibility gates invoke npm
|
|
210
|
+
through each selected Node runtime, prove active 0.13/0.14 work blocks 0.15
|
|
211
|
+
before mutation, and prove upgrade-receipt rollback restores the exact prior
|
|
212
|
+
Skill manifest and ConfigV2 bytes. Existing profiles, adoption evidence,
|
|
213
|
+
executions, delivery packets, snapshots, verification receipts, and closeouts
|
|
214
|
+
remain byte-identical and readable. Neither compatibility nor a completed
|
|
215
|
+
onboarding projection grants authority.
|
|
154
216
|
|
|
155
217
|
## Choose the smallest assurance lane
|
|
156
218
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": "OwlCodaRunKitConfigV2",
|
|
3
3
|
"core": {
|
|
4
4
|
"contractVersion": "0.2",
|
|
5
|
-
"coreVersion": "0.
|
|
5
|
+
"coreVersion": "0.15.0",
|
|
6
6
|
"coreManifestSha256": "sha256:replace-with-installed-core-manifest",
|
|
7
7
|
"coreSourceRef": "artifact:sha256:replace-with-content-addressed-source"
|
|
8
8
|
},
|
package/package.json
CHANGED
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"sha256": "sha256:3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
|
|
37
37
|
},
|
|
38
38
|
"supportedProducerCoreIdentities": [
|
|
39
|
+
{
|
|
40
|
+
"contractVersion": "0.2",
|
|
41
|
+
"coreVersion": "0.15.0",
|
|
42
|
+
"coreManifestSha256": "sha256:06d7616c369b4b3ece3aca32f05c505d4b30781d3ca87a13bd8d7293c1491f64"
|
|
43
|
+
},
|
|
39
44
|
{
|
|
40
45
|
"contractVersion": "0.2",
|
|
41
46
|
"coreVersion": "0.14.0",
|
|
@@ -18,6 +18,11 @@ import { captureWorkspaceSnapshot } from "./workspace-snapshot.mjs";
|
|
|
18
18
|
export { validQuickReceiptShape } from "./quick-receipt-contract.mjs";
|
|
19
19
|
|
|
20
20
|
export const SUPPORTED_QUICK_CORE_IDENTITIES = Object.freeze([
|
|
21
|
+
{
|
|
22
|
+
contractVersion: "0.2",
|
|
23
|
+
coreVersion: "0.15.0",
|
|
24
|
+
coreManifestSha256: "sha256:06d7616c369b4b3ece3aca32f05c505d4b30781d3ca87a13bd8d7293c1491f64",
|
|
25
|
+
},
|
|
21
26
|
{
|
|
22
27
|
contractVersion: "0.2",
|
|
23
28
|
coreVersion: "0.14.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV1",
|
|
3
3
|
"status": "release_candidate_unpublished",
|
|
4
4
|
"packageName": "owlrunkit",
|
|
5
|
-
"candidateVersion": "0.
|
|
5
|
+
"candidateVersion": "0.15.0",
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
7
7
|
"registryRequiredBeforeAdoption": true,
|
|
8
8
|
"exactVersionRequired": true,
|
package/sbom.spdx.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "owlrunkit-0.
|
|
6
|
-
"documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.
|
|
5
|
+
"name": "owlrunkit-0.15.0",
|
|
6
|
+
"documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.15.0",
|
|
7
7
|
"creationInfo": {
|
|
8
8
|
"created": "2026-07-29T00:00:00Z",
|
|
9
9
|
"creators": [
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
{
|
|
15
15
|
"name": "owlrunkit",
|
|
16
16
|
"SPDXID": "SPDXRef-Package-OwlRunKit",
|
|
17
|
-
"versionInfo": "0.
|
|
18
|
-
"downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.
|
|
17
|
+
"versionInfo": "0.15.0",
|
|
18
|
+
"downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.15.0.tgz",
|
|
19
19
|
"filesAnalyzed": false,
|
|
20
20
|
"licenseConcluded": "GPL-3.0-or-later",
|
|
21
21
|
"licenseDeclared": "GPL-3.0-or-later",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
26
26
|
"referenceType": "purl",
|
|
27
|
-
"referenceLocator": "pkg:npm/owlrunkit@0.
|
|
27
|
+
"referenceLocator": "pkg:npm/owlrunkit@0.15.0"
|
|
28
28
|
}
|
|
29
29
|
]
|
|
30
30
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
existsSync,
|
|
4
|
+
lstatSync,
|
|
4
5
|
mkdirSync,
|
|
5
6
|
readFileSync,
|
|
7
|
+
readdirSync,
|
|
6
8
|
realpathSync,
|
|
7
9
|
renameSync,
|
|
8
10
|
rmSync,
|
|
@@ -12,7 +14,7 @@ import { isAbsolute, relative, resolve, win32 } from "node:path";
|
|
|
12
14
|
import { fileURLToPath } from "node:url";
|
|
13
15
|
|
|
14
16
|
export const CONTRACT_VERSION = "0.2";
|
|
15
|
-
export const CORE_VERSION = "0.
|
|
17
|
+
export const CORE_VERSION = "0.15.0";
|
|
16
18
|
export const RUNTIME_ROOT = ".owlcoda/runkit";
|
|
17
19
|
|
|
18
20
|
const CORE_FILES = [
|
|
@@ -72,6 +74,8 @@ const CORE_FILES = [
|
|
|
72
74
|
"schemas/visual-smoke-request-v1.schema.json",
|
|
73
75
|
"schemas/visual-smoke-result-v1.schema.json",
|
|
74
76
|
"schemas/verification-context-v1.schema.json",
|
|
77
|
+
"schemas/verification-finalize-attempt-v1.schema.json",
|
|
78
|
+
"schemas/verification-preflight-v1.schema.json",
|
|
75
79
|
"schemas/verification-plan-v1.schema.json",
|
|
76
80
|
"schemas/verification-receipt-v2.schema.json",
|
|
77
81
|
"schemas/verify-plan-request-v1.schema.json",
|
|
@@ -136,6 +140,22 @@ function migrationReceiptName(migration, fromCore, toCore) {
|
|
|
136
140
|
return `config-v02-core-refresh-${from}-to-${to}.json`;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
function writeMigrationReceiptAppendOnly(receiptsRoot, baseName, receipt) {
|
|
144
|
+
const bytes = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
145
|
+
const stem = baseName.slice(0, -".json".length);
|
|
146
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
147
|
+
const receiptName = attempt === 1
|
|
148
|
+
? baseName
|
|
149
|
+
: `${stem}-attempt-${String(attempt).padStart(3, "0")}.json`;
|
|
150
|
+
try {
|
|
151
|
+
writeFileSync(resolve(receiptsRoot, receiptName), bytes, { flag: "wx" });
|
|
152
|
+
return receiptName;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error?.code !== "EEXIST") throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
139
159
|
function migrateProjectConfig({ runtimeRoot, configPath, config, migration }) {
|
|
140
160
|
validateCoreIdentity(config.core);
|
|
141
161
|
const next = projectConfig();
|
|
@@ -150,8 +170,11 @@ function migrateProjectConfig({ runtimeRoot, configPath, config, migration }) {
|
|
|
150
170
|
};
|
|
151
171
|
const receiptsRoot = resolve(runtimeRoot, "config-migration-receipts");
|
|
152
172
|
mkdirSync(receiptsRoot, { recursive: true });
|
|
153
|
-
const receiptName =
|
|
154
|
-
|
|
173
|
+
const receiptName = writeMigrationReceiptAppendOnly(
|
|
174
|
+
receiptsRoot,
|
|
175
|
+
migrationReceiptName(migration, config.core, next.core),
|
|
176
|
+
receipt,
|
|
177
|
+
);
|
|
155
178
|
atomicWriteJson(configPath, next);
|
|
156
179
|
return `${RUNTIME_ROOT}/config-migration-receipts/${receiptName}`;
|
|
157
180
|
}
|
|
@@ -258,6 +281,98 @@ export function validateProjectConfigV2(config) {
|
|
|
258
281
|
}
|
|
259
282
|
}
|
|
260
283
|
|
|
284
|
+
export function inspectProjectUpgradeSafety({ workspaceRoot } = {}) {
|
|
285
|
+
if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
|
|
286
|
+
throw new Error("workspaceRoot is required.");
|
|
287
|
+
}
|
|
288
|
+
const root = realpathSync(workspaceRoot);
|
|
289
|
+
const executionsRoot = resolve(root, RUNTIME_ROOT, "executions");
|
|
290
|
+
if (!existsSync(executionsRoot)) {
|
|
291
|
+
return {
|
|
292
|
+
status: "safe",
|
|
293
|
+
activeRunIds: [],
|
|
294
|
+
activeLeaseIds: [],
|
|
295
|
+
issues: [],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const executionsStat = lstatSync(executionsRoot);
|
|
299
|
+
if (executionsStat.isSymbolicLink() || !executionsStat.isDirectory()) {
|
|
300
|
+
return {
|
|
301
|
+
status: "unsafe",
|
|
302
|
+
activeRunIds: [],
|
|
303
|
+
activeLeaseIds: [],
|
|
304
|
+
issues: ["RunKit executions root must be a regular directory."],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const activeRunIds = [];
|
|
308
|
+
const activeLeaseIds = [];
|
|
309
|
+
const issues = [];
|
|
310
|
+
const entries = readdirSync(executionsRoot, { withFileTypes: true })
|
|
311
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (entry.isSymbolicLink()) {
|
|
314
|
+
issues.push(`Execution directory must not be a symlink: ${entry.name}`);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (!entry.isDirectory()) continue;
|
|
318
|
+
const executionRoot = resolve(executionsRoot, entry.name);
|
|
319
|
+
const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
|
|
320
|
+
if (!existsSync(closeoutPath)) {
|
|
321
|
+
activeRunIds.push(entry.name);
|
|
322
|
+
} else {
|
|
323
|
+
const closeoutStat = lstatSync(closeoutPath);
|
|
324
|
+
if (closeoutStat.isSymbolicLink() || !closeoutStat.isFile()) {
|
|
325
|
+
issues.push(`Closeout receipt must be a regular file: ${entry.name}`);
|
|
326
|
+
} else {
|
|
327
|
+
try {
|
|
328
|
+
const closeout = JSON.parse(readFileSync(closeoutPath, "utf8"));
|
|
329
|
+
if (closeout?.artifact?.payload?.runId !== entry.name) {
|
|
330
|
+
issues.push(`Closeout receipt runId mismatch: ${entry.name}`);
|
|
331
|
+
}
|
|
332
|
+
} catch {
|
|
333
|
+
issues.push(`Closeout receipt is invalid JSON: ${entry.name}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const leasesRoot = resolve(executionRoot, "leases");
|
|
338
|
+
if (!existsSync(leasesRoot)) continue;
|
|
339
|
+
const leasesStat = lstatSync(leasesRoot);
|
|
340
|
+
if (leasesStat.isSymbolicLink() || !leasesStat.isDirectory()) {
|
|
341
|
+
issues.push(`Lease directory must be a regular directory: ${entry.name}`);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const leaseEntries = readdirSync(leasesRoot, { withFileTypes: true })
|
|
345
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
346
|
+
for (const leaseEntry of leaseEntries) {
|
|
347
|
+
if (!leaseEntry.name.endsWith(".json")) continue;
|
|
348
|
+
if (leaseEntry.isSymbolicLink() || !leaseEntry.isFile()) {
|
|
349
|
+
issues.push(`Lease artifact must be a regular file: ${entry.name}/${leaseEntry.name}`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const lease = JSON.parse(readFileSync(resolve(leasesRoot, leaseEntry.name), "utf8"));
|
|
354
|
+
if (lease.state === "active") {
|
|
355
|
+
activeLeaseIds.push(`${entry.name}:${lease.workItemId ?? leaseEntry.name}`);
|
|
356
|
+
} else if (lease.state !== "released") {
|
|
357
|
+
issues.push(`Lease state is invalid: ${entry.name}/${leaseEntry.name}`);
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
issues.push(`Lease artifact is invalid JSON: ${entry.name}/${leaseEntry.name}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
status: activeRunIds.length === 0
|
|
366
|
+
&& activeLeaseIds.length === 0
|
|
367
|
+
&& issues.length === 0
|
|
368
|
+
? "safe"
|
|
369
|
+
: "unsafe",
|
|
370
|
+
activeRunIds,
|
|
371
|
+
activeLeaseIds,
|
|
372
|
+
issues,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
261
376
|
function validateProducer(producer) {
|
|
262
377
|
if (!isRecord(producer) || typeof producer.adapterKind !== "string" || typeof producer.adapterVersion !== "string") {
|
|
263
378
|
throw new Error("Producer requires adapterKind and adapterVersion.");
|
|
@@ -340,10 +455,10 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
340
455
|
const runtimeRoot = resolve(root, RUNTIME_ROOT);
|
|
341
456
|
const remainder = relative(root, runtimeRoot);
|
|
342
457
|
if (remainder.startsWith("..") || isAbsolute(remainder)) throw new Error("Runtime root escapes the workspace.");
|
|
343
|
-
mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
|
|
344
458
|
const configPath = resolve(runtimeRoot, "config.json");
|
|
345
459
|
const profilesPath = resolve(runtimeRoot, "profiles.json");
|
|
346
460
|
if (!existsSync(configPath)) {
|
|
461
|
+
mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
|
|
347
462
|
atomicWriteJson(configPath, projectConfig());
|
|
348
463
|
} else {
|
|
349
464
|
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
@@ -355,6 +470,16 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
355
470
|
if (config.schemaVersion === "OwlCodaRunKitConfigV2" && currentGate.status === "valid") {
|
|
356
471
|
// The project already points at this exact Core identity.
|
|
357
472
|
} else if (config.schemaVersion === "OwlCodaRunKitConfigV1" && config.core?.contractVersion === "0.1") {
|
|
473
|
+
const safety = inspectProjectUpgradeSafety({ workspaceRoot: root });
|
|
474
|
+
if (safety.status !== "safe") {
|
|
475
|
+
return {
|
|
476
|
+
...safety,
|
|
477
|
+
status: "upgrade_blocked_active_execution",
|
|
478
|
+
exitCode: 2,
|
|
479
|
+
runtimeRoot: RUNTIME_ROOT,
|
|
480
|
+
core: current,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
358
483
|
const migrationReceipt = migrateProjectConfig({
|
|
359
484
|
runtimeRoot,
|
|
360
485
|
configPath,
|
|
@@ -366,6 +491,16 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
366
491
|
}
|
|
367
492
|
return { status: "upgraded", exitCode: 0, runtimeRoot: RUNTIME_ROOT, core: current, migrationReceipt };
|
|
368
493
|
} else if (config.schemaVersion === "OwlCodaRunKitConfigV2" && config.core?.contractVersion === CONTRACT_VERSION) {
|
|
494
|
+
const safety = inspectProjectUpgradeSafety({ workspaceRoot: root });
|
|
495
|
+
if (safety.status !== "safe") {
|
|
496
|
+
return {
|
|
497
|
+
...safety,
|
|
498
|
+
status: "upgrade_blocked_active_execution",
|
|
499
|
+
exitCode: 2,
|
|
500
|
+
runtimeRoot: RUNTIME_ROOT,
|
|
501
|
+
core: current,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
369
504
|
const migrationReceipt = migrateProjectConfig({
|
|
370
505
|
runtimeRoot,
|
|
371
506
|
configPath,
|
|
@@ -380,6 +515,7 @@ export async function initializeProjectRunKit({ workspaceRoot } = {}) {
|
|
|
380
515
|
throw new Error(`Unsupported project config migration: ${config.schemaVersion ?? "missing schemaVersion"}.`);
|
|
381
516
|
}
|
|
382
517
|
}
|
|
518
|
+
mkdirSync(resolve(runtimeRoot, "executions"), { recursive: true });
|
|
383
519
|
if (!existsSync(profilesPath)) {
|
|
384
520
|
writeFileSync(profilesPath, `${JSON.stringify({ schemaVersion: "OwlCodaRunKitProfilesV1", profiles: [] }, null, 2)}\n`);
|
|
385
521
|
}
|