owlrunkit 0.15.0 → 0.15.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.1
4
+
5
+ - Treats preserved active lease bytes in trusted `blocked` or `rejected`
6
+ history as non-active during upgrade safety checks.
7
+ - Validates closeout hashes, decisions, authorization boundaries, accepted
8
+ evidence, and execution pins before trusting historical closeout state.
9
+ - Keeps active executions, invalid closeouts, and accepted closeouts with an
10
+ inconsistent active lease fail-closed.
11
+
3
12
  ## 0.15.0
4
13
 
5
14
  - Makes detected npm launchers resolve to absolute regular files that Formal
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.15.0
14
+ npm install --save-exact owlrunkit@0.15.1
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.15.0
25
+ owlrunkit adopt --workspace /absolute/path/to/project --exact owlrunkit@0.15.1
26
26
  ```
27
27
 
28
28
  `doctor` is read-only and distinguishes offline/DNS timeout, registry
@@ -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.15.0`, bundling Contract `0.2` and
11
- Core `0.15.0`. The root `owlcoda` package has a separate version lifecycle.
10
+ Current release: standalone `owlrunkit@0.15.1`, bundling Contract `0.2` and
11
+ Core `0.15.1`. 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
 
@@ -159,7 +159,7 @@ not grant Git, npm, release, deploy, or foreign-project authority.
159
159
  ## Core v0.15 consolidated onboarding and compatibility
160
160
 
161
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,
162
+ `0.15.1`; new users should not need internal module calls,
163
163
  an external schema validator, or the full Formal lifecycle merely to understand
164
164
  whether a project can adopt RunKit.
165
165
 
@@ -170,7 +170,7 @@ owlrunkit doctor --workspace "$PROJECT_ROOT"
170
170
  owlrunkit profiles detect --workspace "$PROJECT_ROOT" --dry-run
171
171
  owlrunkit profiles validate --workspace "$PROJECT_ROOT"
172
172
  owlrunkit profiles impact --workspace "$PROJECT_ROOT" --changed path/to/file
173
- owlrunkit adopt --workspace "$PROJECT_ROOT" --exact owlrunkit@0.15.0
173
+ owlrunkit adopt --workspace "$PROJECT_ROOT" --exact owlrunkit@0.15.1
174
174
  ```
175
175
 
176
176
  `doctor` and `profiles detect --dry-run` are read-only. Registry diagnostics use
@@ -179,7 +179,7 @@ failures, missing versions and local-install binding mismatches without
179
179
  printing credentials. Profile detection is deterministic evidence for a human
180
180
  decision; it does not write or overwrite project configuration.
181
181
 
182
- `adopt` accepts only an exact `owlrunkit@0.15.0` binding from the official npm
182
+ `adopt` accepts only an exact `owlrunkit@0.15.1` binding from the official npm
183
183
  registry. It recomputes the registry and local install/lock facts, invokes the
184
184
  strict adoption evaluator, and writes typed project evidence only after an
185
185
  eligible result. Worktrees, `file:` dependencies, local tarballs, Git sources,
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "OwlCodaRunKitConfigV2",
3
3
  "core": {
4
4
  "contractVersion": "0.2",
5
- "coreVersion": "0.15.0",
5
+ "coreVersion": "0.15.1",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "owlrunkit",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Project-owned execution, evidence, verification, and recovery for coding agents.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "yeemio <owlclaw@163.com>",
@@ -36,6 +36,11 @@
36
36
  "sha256": "sha256:3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
37
37
  },
38
38
  "supportedProducerCoreIdentities": [
39
+ {
40
+ "contractVersion": "0.2",
41
+ "coreVersion": "0.15.1",
42
+ "coreManifestSha256": "sha256:e8ca57522a8e473da356ceb3768bb650267894638d450d5e49463ab6f51c752b"
43
+ },
39
44
  {
40
45
  "contractVersion": "0.2",
41
46
  "coreVersion": "0.15.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.1",
24
+ coreManifestSha256: "sha256:e8ca57522a8e473da356ceb3768bb650267894638d450d5e49463ab6f51c752b",
25
+ },
21
26
  {
22
27
  contractVersion: "0.2",
23
28
  coreVersion: "0.15.0",
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "OwlCodaRunKitRegistryReleasePolicyV1",
3
3
  "status": "release_candidate_unpublished",
4
4
  "packageName": "owlrunkit",
5
- "candidateVersion": "0.15.0",
5
+ "candidateVersion": "0.15.1",
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.15.0",
6
- "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.15.0",
5
+ "name": "owlrunkit-0.15.1",
6
+ "documentNamespace": "https://owlcoda.com/spdx/owlrunkit/0.15.1",
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.15.0",
18
- "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.15.0.tgz",
17
+ "versionInfo": "0.15.1",
18
+ "downloadLocation": "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.15.1.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.15.0"
27
+ "referenceLocator": "pkg:npm/owlrunkit@0.15.1"
28
28
  }
29
29
  ]
30
30
  }
@@ -14,7 +14,7 @@ import { isAbsolute, relative, resolve, win32 } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
15
 
16
16
  export const CONTRACT_VERSION = "0.2";
17
- export const CORE_VERSION = "0.15.0";
17
+ export const CORE_VERSION = "0.15.1";
18
18
  export const RUNTIME_ROOT = ".owlcoda/runkit";
19
19
 
20
20
  const CORE_FILES = [
@@ -234,9 +234,15 @@ export function validateLeaseOwnedPaths(paths) {
234
234
 
235
235
  export function validateExecutionPin({ expected, actual } = {}) {
236
236
  const fields = ["contractVersion", "coreVersion", "coreManifestSha256", "coreSourceRef"];
237
- const malformed = fields.filter((field) => typeof expected?.[field] !== "string" || typeof actual?.[field] !== "string");
238
- if (malformed.length > 0) {
239
- return { status: "invalid_input", exitCode: 3, issues: malformed.map((field) => `missing engine pin field: ${field}`) };
237
+ try {
238
+ validateCoreIdentity(expected, { requireSourceBinding: false });
239
+ validateCoreIdentity(actual, { requireSourceBinding: false });
240
+ } catch (error) {
241
+ return {
242
+ status: "invalid_input",
243
+ exitCode: 3,
244
+ issues: [error instanceof Error ? `invalid engine pin: ${error.message}` : String(error)],
245
+ };
240
246
  }
241
247
  const issues = fields.filter((field) => expected[field] !== actual[field]).map((field) => `${field} changed during execution`);
242
248
  return issues.length === 0
@@ -244,11 +250,27 @@ export function validateExecutionPin({ expected, actual } = {}) {
244
250
  : { status: "engine_changed_during_execution", exitCode: 2, issues };
245
251
  }
246
252
 
247
- function validateCoreIdentity(core) {
253
+ function validateCoreIdentity(core, { requireSourceBinding = true } = {}) {
248
254
  if (!isRecord(core)) throw new Error("Core identity must be an object.");
255
+ const coreKeys = ["contractVersion", "coreManifestSha256", "coreSourceRef", "coreVersion"];
256
+ const unsupportedCoreKeys = Object.keys(core).filter(key => !coreKeys.includes(key));
257
+ if (unsupportedCoreKeys.length > 0) {
258
+ throw new Error(`Core identity contains unsupported field: ${unsupportedCoreKeys[0]}`);
259
+ }
249
260
  if (typeof core.contractVersion !== "string" || typeof core.coreVersion !== "string") throw new Error("Core identity requires contractVersion and coreVersion.");
261
+ if (!new Set(["0.1", CONTRACT_VERSION]).has(core.contractVersion)) {
262
+ throw new Error("Core identity contractVersion must be a supported RunKit contract version.");
263
+ }
264
+ if (!/^\d+\.\d+\.\d+$/u.test(core.coreVersion)) {
265
+ throw new Error("Core identity coreVersion must be a three-part semantic version.");
266
+ }
250
267
  if (!isSha256Ref(core.coreManifestSha256)) throw new Error("Core identity requires coreManifestSha256.");
251
- if (typeof core.coreSourceRef !== "string" || core.coreSourceRef.length === 0) throw new Error("Core identity requires coreSourceRef.");
268
+ if (typeof core.coreSourceRef !== "string" || core.coreSourceRef.length === 0) {
269
+ throw new Error("Core identity coreSourceRef must be a non-empty string.");
270
+ }
271
+ if (requireSourceBinding && core.coreSourceRef !== `artifact:${core.coreManifestSha256}`) {
272
+ throw new Error("Core identity coreSourceRef must bind coreManifestSha256.");
273
+ }
252
274
  }
253
275
 
254
276
  export function validateProjectConfigV2(config) {
@@ -281,6 +303,110 @@ export function validateProjectConfigV2(config) {
281
303
  }
282
304
  }
283
305
 
306
+ function inspectTrustedCloseout(executionRoot, runId) {
307
+ const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
308
+ try {
309
+ const closeoutStat = lstatSync(closeoutPath);
310
+ if (closeoutStat.isSymbolicLink() || !closeoutStat.isFile()) {
311
+ return {
312
+ trusted: false,
313
+ decision: null,
314
+ issues: [`Closeout receipt must be a regular file: ${runId}`],
315
+ };
316
+ }
317
+ const closeout = JSON.parse(readFileSync(closeoutPath, "utf8"));
318
+ if (closeout?.artifact?.payload?.runId !== runId) {
319
+ return {
320
+ trusted: false,
321
+ decision: null,
322
+ issues: [`Closeout receipt runId mismatch: ${runId}`],
323
+ };
324
+ }
325
+ const artifactGate = validateCoreArtifact(closeout.artifact);
326
+ if (!artifactGate.valid) {
327
+ return {
328
+ trusted: false,
329
+ decision: null,
330
+ issues: artifactGate.issues.map(issue => `Closeout receipt is invalid: ${runId}: ${issue}`),
331
+ };
332
+ }
333
+ if (closeout.acceptanceSha256 !== artifactGate.acceptanceSha256
334
+ || closeout.artifactSha256 !== artifactGate.artifactSha256) {
335
+ return {
336
+ trusted: false,
337
+ decision: null,
338
+ issues: [`Closeout receipt hash mismatch: ${runId}`],
339
+ };
340
+ }
341
+ const payload = closeout.artifact.payload;
342
+ if (!new Set(["accepted", "rejected", "blocked"]).has(payload.decision)
343
+ || payload.authorizationGranted !== false) {
344
+ return {
345
+ trusted: false,
346
+ decision: null,
347
+ issues: [`Closeout receipt decision or authorization is invalid: ${runId}`],
348
+ };
349
+ }
350
+ const expectedPayloadKeys = payload.decision === "accepted"
351
+ ? ["authorizationGranted", "decision", "runId", "verification"]
352
+ : ["authorizationGranted", "decision", "runId"];
353
+ const unsupportedPayloadKeys = Object.keys(payload)
354
+ .filter(key => !expectedPayloadKeys.includes(key));
355
+ if (unsupportedPayloadKeys.length > 0) {
356
+ return {
357
+ trusted: false,
358
+ decision: null,
359
+ issues: [
360
+ `Closeout receipt payload contains unsupported field: ${runId}: ${unsupportedPayloadKeys[0]}`,
361
+ ],
362
+ };
363
+ }
364
+ const verificationIssues = acceptedCloseoutVerificationIssues(closeout.artifact);
365
+ if (verificationIssues.length > 0) {
366
+ return {
367
+ trusted: false,
368
+ decision: null,
369
+ issues: verificationIssues.map(issue => `Closeout receipt is invalid: ${runId}: ${issue}`),
370
+ };
371
+ }
372
+ const pinPath = resolve(executionRoot, "engine-pin.json");
373
+ const pinStat = lstatSync(pinPath);
374
+ if (pinStat.isSymbolicLink() || !pinStat.isFile()) {
375
+ return {
376
+ trusted: false,
377
+ decision: null,
378
+ issues: [`Execution engine pin must be a regular file: ${runId}`],
379
+ };
380
+ }
381
+ const pinGate = validateExecutionPin({
382
+ expected: JSON.parse(readFileSync(pinPath, "utf8")),
383
+ actual: closeout.artifact.core,
384
+ });
385
+ if (pinGate.status !== "valid") {
386
+ return {
387
+ trusted: false,
388
+ decision: null,
389
+ issues: pinGate.issues.map(issue => `Closeout engine pin mismatch: ${runId}: ${issue}`),
390
+ };
391
+ }
392
+ return {
393
+ trusted: true,
394
+ decision: payload.decision,
395
+ issues: [],
396
+ };
397
+ } catch (error) {
398
+ return {
399
+ trusted: false,
400
+ decision: null,
401
+ issues: [
402
+ `Closeout receipt or engine pin is invalid: ${runId}: ${
403
+ error instanceof Error ? error.message : String(error)
404
+ }`,
405
+ ],
406
+ };
407
+ }
408
+ }
409
+
284
410
  export function inspectProjectUpgradeSafety({ workspaceRoot } = {}) {
285
411
  if (typeof workspaceRoot !== "string" || workspaceRoot.length === 0) {
286
412
  throw new Error("workspaceRoot is required.");
@@ -317,23 +443,16 @@ export function inspectProjectUpgradeSafety({ workspaceRoot } = {}) {
317
443
  if (!entry.isDirectory()) continue;
318
444
  const executionRoot = resolve(executionsRoot, entry.name);
319
445
  const closeoutPath = resolve(executionRoot, "closeout-receipt.json");
446
+ let skipHistoricalLeases = false;
320
447
  if (!existsSync(closeoutPath)) {
321
448
  activeRunIds.push(entry.name);
322
449
  } 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
- }
450
+ const closeout = inspectTrustedCloseout(executionRoot, entry.name);
451
+ issues.push(...closeout.issues);
452
+ skipHistoricalLeases = closeout.trusted
453
+ && new Set(["rejected", "blocked"]).has(closeout.decision);
336
454
  }
455
+ if (skipHistoricalLeases) continue;
337
456
  const leasesRoot = resolve(executionRoot, "leases");
338
457
  if (!existsSync(leasesRoot)) continue;
339
458
  const leasesStat = lstatSync(leasesRoot);
@@ -377,6 +496,14 @@ function validateProducer(producer) {
377
496
  if (!isRecord(producer) || typeof producer.adapterKind !== "string" || typeof producer.adapterVersion !== "string") {
378
497
  throw new Error("Producer requires adapterKind and adapterVersion.");
379
498
  }
499
+ if (producer.adapterKind.length === 0 || producer.adapterVersion.length === 0) {
500
+ throw new Error("Producer adapterKind and adapterVersion must be non-empty strings.");
501
+ }
502
+ const producerKeys = ["adapterKind", "adapterVersion"];
503
+ const unsupportedProducerKeys = Object.keys(producer).filter(key => !producerKeys.includes(key));
504
+ if (unsupportedProducerKeys.length > 0) {
505
+ throw new Error(`Producer contains unsupported field: ${unsupportedProducerKeys[0]}`);
506
+ }
380
507
  }
381
508
 
382
509
  function validateExtensions(extensions) {
@@ -416,7 +543,18 @@ export function createCoreArtifact({ core = currentCoreIdentity(), producer, pay
416
543
  export function validateCoreArtifact(artifact) {
417
544
  try {
418
545
  if (!isRecord(artifact) || !new Set(["RunKitCoreArtifactV1", "RunKitCoreArtifactV2"]).has(artifact.schemaVersion)) throw new Error("Unsupported core artifact schemaVersion.");
546
+ const artifactKeys = ["core", "extensions", "payload", "producer", "schemaVersion"];
547
+ const unsupportedArtifactKeys = Object.keys(artifact).filter(key => !artifactKeys.includes(key));
548
+ if (unsupportedArtifactKeys.length > 0) {
549
+ throw new Error(`Core artifact contains unsupported field: ${unsupportedArtifactKeys[0]}`);
550
+ }
419
551
  validateCoreIdentity(artifact.core);
552
+ const expectedContractVersion = artifact.schemaVersion === "RunKitCoreArtifactV1"
553
+ ? "0.1"
554
+ : CONTRACT_VERSION;
555
+ if (artifact.core.contractVersion !== expectedContractVersion) {
556
+ throw new Error(`Core artifact ${artifact.schemaVersion} requires contractVersion ${expectedContractVersion}.`);
557
+ }
420
558
  validateProducer(artifact.producer);
421
559
  if (!isRecord(artifact.payload)) throw new Error("Core artifact payload must be an object.");
422
560
  validateExtensions(artifact.extensions);