owlrunkit 0.16.0 → 0.17.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +40 -3
  2. package/README.md +61 -12
  3. package/docs/architecture/OWLCODA_RUNKIT_017_AGENT_NATIVE_PRODUCT_DEFINITION.md +154 -0
  4. package/integrations/codex/skills/owlcoda-runkit/SKILL.md +74 -16
  5. package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
  6. package/package.json +1 -1
  7. package/packages/attest/distribution-manifest-v1.json +10 -0
  8. package/packages/attest/src/quick.mjs +10 -0
  9. package/registry-release-policy-v1.json +5 -5
  10. package/sbom.spdx.json +5 -5
  11. package/scripts/runkit-contract/core-contract.mjs +12 -2
  12. package/scripts/runkit-contract/fleet-discovery.mjs +768 -26
  13. package/scripts/runkit-contract/formal-workflow.mjs +201 -0
  14. package/scripts/runkit-contract/lifecycle-orchestration.mjs +7 -2
  15. package/scripts/runkit-contract/project-cli-resolver.mjs +3 -0
  16. package/scripts/runkit-contract/project-control-parser.mjs +62 -3
  17. package/scripts/runkit-contract/runkit-cli.mjs +162 -13
  18. package/scripts/runkit-contract/schemas/core-successor-plan-v1.schema.json +14 -2
  19. package/scripts/runkit-contract/schemas/fleet-discovery-v1.schema.json +33 -2
  20. package/scripts/runkit-contract/schemas/team-project-definition-v1.schema.json +108 -0
  21. package/scripts/runkit-contract/schemas/team-project-event-v1.schema.json +99 -0
  22. package/scripts/runkit-contract/schemas/team-project-status-v1.schema.json +50 -0
  23. package/scripts/runkit-contract/schemas/verification-envelope-v1.schema.json +102 -0
  24. package/scripts/runkit-contract/sync-core016-release-identity.mjs +9 -4
  25. package/scripts/runkit-contract/sync-release-identity.mjs +224 -0
  26. package/scripts/runkit-contract/team-project.mjs +1006 -0
  27. package/scripts/runkit-contract/templates/team-project-definition.json +30 -0
  28. package/scripts/runkit-contract/templates/team-project-event.json +9 -0
  29. package/scripts/runkit-contract/templates/verification-envelope.json +28 -0
  30. package/scripts/runkit-contract/verification-envelope-check.mjs +45 -0
  31. package/scripts/runkit-contract/verification-envelope.mjs +846 -0
@@ -6,6 +6,7 @@ import {
6
6
  realpathSync,
7
7
  } from "node:fs";
8
8
  import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
9
10
 
10
11
  import {
11
12
  acceptedCloseoutVerificationIssues,
@@ -33,6 +34,16 @@ import {
33
34
  } from "./source-candidate.mjs";
34
35
  import { validateReceiptLineage } from "./receipt-lineage.mjs";
35
36
  import { verificationContextFingerprint } from "./verification-context.mjs";
37
+ import {
38
+ runVerificationEnvelopeV1,
39
+ validateVerificationEnvelopeV1,
40
+ verifyVerificationEnvelopeReceiptV1,
41
+ } from "./verification-envelope.mjs";
42
+
43
+ const VERIFICATION_ENVELOPE_CHECKER = fileURLToPath(new URL(
44
+ "./verification-envelope-check.mjs",
45
+ import.meta.url,
46
+ ));
36
47
 
37
48
  const FORBIDDEN_EXTERNAL_TOOLS = new Set([
38
49
  "curl",
@@ -623,6 +634,184 @@ export function recordFormalCheckV1(options) {
623
634
  );
624
635
  }
625
636
 
637
+ async function recordFormalEnvelopeCheckWithinControlTransaction({
638
+ workspaceRoot,
639
+ runId,
640
+ workItemId,
641
+ checkId,
642
+ envelope,
643
+ hooks = {},
644
+ }) {
645
+ safeIdentifier(checkId, "checkId");
646
+ const root = realpathSync(workspaceRoot);
647
+ const { executionRoot, pinGate } = loadActiveExecution(root, runId);
648
+ if (pinGate.status !== "valid") return { ...pinGate, authorizationGranted: false };
649
+ const validatedEnvelope = validateVerificationEnvelopeV1({
650
+ workspaceRoot: root,
651
+ envelope,
652
+ });
653
+ const receiptPath = path.join(checkRoot(executionRoot), `${checkId}.json`);
654
+ const candidateResult = resumeOrFreezeCandidate({
655
+ workspaceRoot: root,
656
+ executionRoot,
657
+ runId,
658
+ workItemId,
659
+ checkId,
660
+ });
661
+ const candidate = candidateResult.candidate;
662
+ if (candidate.status !== "source_candidate_frozen") return candidate;
663
+ hooks.afterCandidatePersist?.();
664
+ const candidateDocument = readJson(path.join(root, candidate.candidatePath));
665
+ const envelopePath = path.join(
666
+ executionRoot,
667
+ "formal-envelopes",
668
+ `${checkId}.json`,
669
+ );
670
+ const envelopeResult = writeOrResumeExactJson(
671
+ envelopePath,
672
+ validatedEnvelope.envelope,
673
+ "Formal Verification Envelope",
674
+ );
675
+ const envelopeArtifactRoot = path.join(
676
+ executionRoot,
677
+ "verification-envelopes",
678
+ checkId,
679
+ );
680
+ const envelopeReceipt = await runVerificationEnvelopeV1({
681
+ workspaceRoot: root,
682
+ envelope: envelopeResult.value,
683
+ artifactRoot: envelopeArtifactRoot,
684
+ });
685
+ hooks.afterEnvelopePersist?.();
686
+ const commandArgv = [
687
+ realpathSync(process.execPath),
688
+ VERIFICATION_ENVELOPE_CHECKER,
689
+ "--workspace",
690
+ root,
691
+ "--receipt",
692
+ envelopeReceipt.receiptPath,
693
+ ];
694
+ const verificationCommandPolicy = {
695
+ schemaVersion: "OwlCodaRunKitFormalVerificationCommandPolicyV1",
696
+ mode: "verification_envelope_v1",
697
+ riskClassification: "enforced_verification_envelope",
698
+ profileCommandIds: [],
699
+ executable: commandArgv[0],
700
+ argv: commandArgv.slice(1),
701
+ envelopeSha256: validatedEnvelope.envelopeSha256,
702
+ envelopeReceiptSha256: envelopeReceipt.receiptSha256,
703
+ authorizationGranted: false,
704
+ };
705
+ const context = derivedVerificationContext(root);
706
+ const request = {
707
+ schemaVersion: "OwlCodaRunKitSnapshotRequestV1",
708
+ snapshotId: `${checkId}-snapshot`,
709
+ mode: "project",
710
+ targetRoot: root,
711
+ cwd: ".",
712
+ executable: verificationCommandPolicy.executable,
713
+ argv: verificationCommandPolicy.argv,
714
+ launcherVersion: "owlrunkit-formal-envelope-v1",
715
+ toolVersions: structuredClone(context.toolchains),
716
+ selectedPaths: candidateDocument.sourceManifest.entries
717
+ .filter((entry) => entry.operation !== "deleted")
718
+ .map((entry) => entry.path)
719
+ .sort(),
720
+ statusMode: "porcelain-v1-z-untracked-all-runkit-excluded",
721
+ verificationContext: context,
722
+ };
723
+ const requestPath = path.join(
724
+ executionRoot,
725
+ "formal-check-requests",
726
+ `${checkId}.json`,
727
+ );
728
+ const requestResult = writeOrResumeExactJson(
729
+ requestPath,
730
+ request,
731
+ "Formal check request",
732
+ );
733
+ hooks.afterRequestPersist?.();
734
+ const snapshotResult = resumeSnapshot({
735
+ workspaceRoot: root,
736
+ executionRoot,
737
+ runId,
738
+ request: requestResult.value,
739
+ candidateDocument,
740
+ });
741
+ const snapshot = snapshotResult.snapshot;
742
+ hooks.afterSnapshotPersist?.();
743
+ const savedSnapshot = readJson(path.join(root, snapshot.snapshotPath));
744
+ const existingReceipt = existsSync(receiptPath)
745
+ ? readRegularJson(receiptPath, "Formal check receipt")
746
+ : null;
747
+ const sequence = existingReceipt?.sequence
748
+ ?? readChecks(executionRoot).length + 1;
749
+ const status = snapshot.status === "snapshot_passed"
750
+ && envelopeReceipt.formalEligible
751
+ ? "formal_check_passed"
752
+ : "formal_check_failed";
753
+ const receipt = {
754
+ schemaVersion: "OwlCodaRunKitFormalCheckV1",
755
+ runId,
756
+ checkId,
757
+ sequence,
758
+ status,
759
+ candidatePath: candidate.candidatePath,
760
+ candidateSha256: candidate.candidateSha256,
761
+ payloadSha256: candidate.payloadSha256,
762
+ sourceFingerprint: candidate.sourceFingerprint,
763
+ verificationContext: context,
764
+ verificationContextFingerprint: verificationContextFingerprint(context),
765
+ snapshotRequestPath: relativeToWorkspace(root, requestPath),
766
+ snapshotPath: snapshot.snapshotPath,
767
+ commandExitCode: savedSnapshot.command.exitCode,
768
+ verificationCommandPolicy: {
769
+ schemaVersion: verificationCommandPolicy.schemaVersion,
770
+ mode: verificationCommandPolicy.mode,
771
+ riskClassification: verificationCommandPolicy.riskClassification,
772
+ profileCommandIds: verificationCommandPolicy.profileCommandIds,
773
+ envelopeSha256: verificationCommandPolicy.envelopeSha256,
774
+ envelopeReceiptSha256: verificationCommandPolicy.envelopeReceiptSha256,
775
+ authorizationGranted: false,
776
+ },
777
+ envelopePath: relativeToWorkspace(root, envelopePath),
778
+ envelopeSha256: validatedEnvelope.envelopeSha256,
779
+ envelopeReceiptPath: relativeToWorkspace(root, envelopeReceipt.receiptPath),
780
+ envelopeReceiptSha256: envelopeReceipt.receiptSha256,
781
+ envelopeFormalEligible: envelopeReceipt.formalEligible,
782
+ finalized: false,
783
+ authorizationGranted: false,
784
+ };
785
+ const receiptResult = writeOrResumeExactJson(
786
+ receiptPath,
787
+ receipt,
788
+ "Formal check receipt",
789
+ );
790
+ hooks.afterReceiptPersist?.();
791
+ return {
792
+ ...receiptResult.value,
793
+ exitCode: status === "formal_check_passed" ? 0 : 2,
794
+ checkReceiptPath: relativeToWorkspace(root, receiptPath),
795
+ resumed: candidateResult.resumed
796
+ || envelopeResult.resumed
797
+ || envelopeReceipt.resumed
798
+ || requestResult.resumed
799
+ || snapshotResult.resumed
800
+ || receiptResult.resumed,
801
+ };
802
+ }
803
+
804
+ export function recordFormalEnvelopeCheckV1(options) {
805
+ const workspaceRoot = realpathSync(options.workspaceRoot);
806
+ return withControlTransaction(
807
+ workspaceRoot,
808
+ () => recordFormalEnvelopeCheckWithinControlTransaction({
809
+ ...options,
810
+ workspaceRoot,
811
+ }),
812
+ );
813
+ }
814
+
626
815
  function finalizeFormalChecksWithinControlTransaction({
627
816
  workspaceRoot,
628
817
  runId,
@@ -651,6 +840,18 @@ function finalizeFormalChecksWithinControlTransaction({
651
840
  if (latest.status !== "formal_check_passed" || selected.length === 0) {
652
841
  throw new Error("Formal finish requires the latest source to have a passed check.");
653
842
  }
843
+ for (const check of selected) {
844
+ if (!check.envelopeReceiptPath) continue;
845
+ const verifiedEnvelope = verifyVerificationEnvelopeReceiptV1({
846
+ workspaceRoot: root,
847
+ receiptPath: path.join(root, check.envelopeReceiptPath),
848
+ });
849
+ if (!verifiedEnvelope.formalEligible
850
+ || verifiedEnvelope.receiptSha256 !== check.envelopeReceiptSha256
851
+ || verifiedEnvelope.envelopeSha256 !== check.envelopeSha256) {
852
+ throw new Error(`Formal finish requires a valid Formal-eligible envelope receipt: ${check.checkId}.`);
853
+ }
854
+ }
654
855
  const request = {
655
856
  schemaVersion: "OwlCodaRunKitFinalizeRequestV1",
656
857
  receiptId: `${finalizeId}-receipt`,
@@ -486,6 +486,7 @@ export function activeAcceptedGate({ workspaceRoot, runId }) {
486
486
  }
487
487
  }
488
488
  const distinctMatchingPackets = collapseByteIdenticalDeliveryCandidates(matchingPackets);
489
+ const declaredSourceArtifact = validated.active.receipt.sourceArtifact;
489
490
  let matchingCandidate = null;
490
491
  if (distinctMatchingPackets.length === 0) {
491
492
  const candidateRoot = path.join(executionRoot, "source-candidates");
@@ -524,7 +525,12 @@ export function activeAcceptedGate({ workspaceRoot, runId }) {
524
525
  candidates.map(({ candidate }) => candidate.sourceManifest.sha256),
525
526
  );
526
527
  if (manifestFingerprints.size === 1) {
527
- [matchingCandidate] = candidates;
528
+ matchingCandidate = candidates.find(({ candidatePath }) => (
529
+ declaredSourceArtifact?.kind === "source_candidate_v2"
530
+ && declaredSourceArtifact.path
531
+ === relativeToWorkspace(workspaceRoot, candidatePath)
532
+ && declaredSourceArtifact.sha256 === sha256(readFileSync(candidatePath))
533
+ )) ?? candidates[0];
528
534
  }
529
535
  }
530
536
  if (
@@ -537,7 +543,6 @@ export function activeAcceptedGate({ workspaceRoot, runId }) {
537
543
  throw new Error("Accepted finish requires one fresh source binding matching the active verification gate; source drift remains unresolved.");
538
544
  }
539
545
  const [matchingPacket] = distinctMatchingPackets;
540
- const declaredSourceArtifact = validated.active.receipt.sourceArtifact;
541
546
  if (declaredSourceArtifact !== undefined) {
542
547
  const selectedPath = matchingCandidate === null
543
548
  ? matchingPacket.packetPath
@@ -18,6 +18,9 @@ const OFFICIAL_CLI_ENTRYPOINT =
18
18
  const EXACT_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
19
19
  const SHA256_REF = /^sha256:[a-f0-9]{64}$/;
20
20
  const TRUSTED_PRIOR_CORE_IDENTITIES = new Map([
21
+ ["0.16.0", new Set([
22
+ "sha256:320fb1d97b4459d1a14b0b67807dcf2bc6b03970492cb9fb2c6245b17912c81e",
23
+ ])],
21
24
  ["0.15.1", new Set([
22
25
  "sha256:e8ca57522a8e473da356ceb3768bb650267894638d450d5e49463ab6f51c752b",
23
26
  ])],
@@ -140,7 +140,40 @@ function allowedKeys(value, expected) {
140
140
  return Object.keys(value).every(key => expected.includes(key));
141
141
  }
142
142
 
143
- function inspectLeases(root, executionRoot, runId, contract) {
143
+ function validateHistoricalLeaseOwnedPaths(paths, contract) {
144
+ try {
145
+ return contract.validateLeaseOwnedPaths(paths);
146
+ } catch {
147
+ if (!Array.isArray(paths) || paths.length === 0) throw new Error("invalid");
148
+ const normalized = [...new Set(paths.map(value => {
149
+ if (
150
+ typeof value !== "string"
151
+ || value.length === 0
152
+ || path.isAbsolute(value)
153
+ || path.win32.isAbsolute(value)
154
+ || value.includes("\\")
155
+ ) throw new Error("invalid");
156
+ const segments = value.split("/");
157
+ if (segments.some(segment => (
158
+ segment.length === 0 || segment === "." || segment === ".."
159
+ ))) throw new Error("invalid");
160
+ if (
161
+ value.startsWith(`${contract.runtimeRoot}/`)
162
+ && value !== `${contract.runtimeRoot}/profiles.json`
163
+ ) throw new Error("invalid");
164
+ return value;
165
+ }))].sort(compareCodeUnits);
166
+ return normalized;
167
+ }
168
+ }
169
+
170
+ function inspectLeases(
171
+ root,
172
+ executionRoot,
173
+ runId,
174
+ contract,
175
+ { historicalAccepted = false, expectedReleasedLeaseIds = null } = {},
176
+ ) {
144
177
  const leasesRoot = path.join(executionRoot, "leases");
145
178
  if (!hasFilesystemEntry(leasesRoot)) {
146
179
  return {
@@ -200,7 +233,9 @@ function inspectLeases(root, executionRoot, runId, contract) {
200
233
  issues.push(`Lease attempt is invalid: ${runId}/${entry.name}`);
201
234
  }
202
235
  try {
203
- const ownedPaths = contract.validateLeaseOwnedPaths(lease.ownedPaths);
236
+ const ownedPaths = historicalAccepted
237
+ ? validateHistoricalLeaseOwnedPaths(lease.ownedPaths, contract)
238
+ : contract.validateLeaseOwnedPaths(lease.ownedPaths);
204
239
  if (ownedPaths.some(ownedPath => (
205
240
  ownedPath.includes("*")
206
241
  && (!ownedPath.endsWith("/**") || ownedPath.slice(0, -3).includes("*"))
@@ -232,6 +267,14 @@ function inspectLeases(root, executionRoot, runId, contract) {
232
267
  .map(lease => lease.workItemId)
233
268
  .filter(workItemId => typeof workItemId === "string")
234
269
  .sort(compareCodeUnits);
270
+ if (
271
+ historicalAccepted
272
+ && Array.isArray(expectedReleasedLeaseIds)
273
+ && JSON.stringify(releasedWorkItemIds)
274
+ !== JSON.stringify([...expectedReleasedLeaseIds].sort(compareCodeUnits))
275
+ ) {
276
+ issues.push(`Historical accepted released lease binding mismatch: ${runId}`);
277
+ }
235
278
  return {
236
279
  status: issues.length > 0
237
280
  ? "invalid"
@@ -337,6 +380,11 @@ function inspectCloseout(root, executionRoot, runId, pin, contract) {
337
380
  }
338
381
  : {}),
339
382
  core: issues.length === 0 ? closeout.artifact.core : null,
383
+ releasedLeaseIds: issues.length === 0
384
+ && payload.decision === "accepted"
385
+ && Array.isArray(payload.verification?.releasedLeaseIds)
386
+ ? [...payload.verification.releasedLeaseIds]
387
+ : null,
340
388
  issues,
341
389
  };
342
390
  }
@@ -746,7 +794,18 @@ export function parseProjectControlState({
746
794
  && contract.validateExecutionPin({ expected: pin, actual: currentCore }).status !== "valid";
747
795
  const preserveHistoricalLeases = lifecycle === "closed"
748
796
  && new Set(["blocked", "rejected"]).has(closeout.decision);
749
- const inspectedLease = inspectLeases(root, executionRoot, runId, contract);
797
+ const inspectedLease = inspectLeases(
798
+ root,
799
+ executionRoot,
800
+ runId,
801
+ contract,
802
+ {
803
+ historicalAccepted: historical
804
+ && lifecycle === "closed"
805
+ && closeout.decision === "accepted",
806
+ expectedReleasedLeaseIds: closeout.releasedLeaseIds,
807
+ },
808
+ );
750
809
  const lease = preserveHistoricalLeases && inspectedLease.status !== "invalid"
751
810
  ? {
752
811
  ...inspectedLease,
@@ -65,6 +65,7 @@ import {
65
65
  import {
66
66
  finalizeFormalChecksV1,
67
67
  recordFormalCheckV1,
68
+ recordFormalEnvelopeCheckV1,
68
69
  resumeCompletedFormalFinishV1,
69
70
  validateLegacyVerificationSideEffectsV1,
70
71
  } from "./formal-workflow.mjs";
@@ -93,7 +94,10 @@ import {
93
94
  } from "./profile-onboarding.mjs";
94
95
  import {
95
96
  discoverFleet,
97
+ inspectFleetRegistry,
96
98
  registerFleetCoverageRoot,
99
+ replaceFleetRegistry,
100
+ rollbackFleetRegistry,
97
101
  } from "./fleet-discovery.mjs";
98
102
  import { routeRunKitAssuranceV1 } from "./assurance-router.mjs";
99
103
  import { deriveHumanStatusFromInspectV1 } from "./human-status.mjs";
@@ -132,6 +136,13 @@ import {
132
136
  runRegistryAdoption,
133
137
  } from "./registry-adoption.mjs";
134
138
  import { OFFICIAL_NPM_REGISTRY } from "./registry-adoption-gate.mjs";
139
+ import {
140
+ appendTeamProjectEventV1,
141
+ buildTeamProjectTakeoverV1,
142
+ formatTeamProjectStatusHumanV1,
143
+ initializeTeamProjectV1,
144
+ readTeamProjectStatusV1,
145
+ } from "./team-project.mjs";
135
146
 
136
147
  const TOP_LEVEL_HELP = `OwlRunKit ${CORE_VERSION}
137
148
 
@@ -162,20 +173,21 @@ Two-stage deployment
162
173
  owlrunkit deployment execute --workspace . [--resume] --prepare <receipt> --owner-decision <decision-v1.json> --owner-authority <signed-v2.json> --manifest <remote.json>
163
174
 
164
175
  Fleet and status
165
- owlrunkit fleet register-root --fleet-root <directory>
166
- owlrunkit fleet discover
167
- owlrunkit status --workspace .
168
-
176
+ owlrunkit fleet <register-root|inspect-registry> [options]
177
+ owlrunkit fleet replace-registry --request <file> --receipt <file> [--dry-run]
178
+ owlrunkit fleet rollback-registry --receipt <file> --rollback-receipt <file>
179
+ owlrunkit fleet discover; owlrunkit status --workspace .
180
+
181
+ Agent team projects
182
+ owlrunkit project <init|assign|checkpoint|handoff|decision|capture|status|takeover> [options]
183
+ Durable Agent work, dependencies, decisions, gates, progress, and handoffs.
169
184
  Core successor
170
185
  owlrunkit core-successor <plan|apply|resume> --workspace . [...]
171
186
 
172
187
  Other onboarding commands:
173
- owlrunkit profiles validate --workspace .
174
- owlrunkit profiles impact --workspace . --changed <path>
188
+ owlrunkit profiles validate --workspace .; owlrunkit profiles impact --workspace . --changed <path>
175
189
  owlrunkit inspect --workspace . --json
176
-
177
- Quick receipts cannot become Formal acceptance.
178
- Formal accepted does not grant Git or release authority.
190
+ Formal accepted does not grant Git or release authority; Quick cannot become Formal.
179
191
  `;
180
192
 
181
193
  const COMMAND_HELP = Object.freeze({
@@ -193,12 +205,33 @@ Required:
193
205
  `,
194
206
  fleet: `Usage:
195
207
  owlrunkit fleet register-root --fleet-root <directory>
208
+ owlrunkit fleet inspect-registry [--registry <file>]
209
+ owlrunkit fleet replace-registry --request <file> --receipt <file> [--registry <file>] [--dry-run]
210
+ owlrunkit fleet rollback-registry --receipt <file> --rollback-receipt <file> [--registry <file>]
196
211
  owlrunkit fleet discover [--fleet-root <directory>] [--fleet-manifest <file>]
197
212
  `,
198
213
  "fleet register-root": `Usage:
199
214
  owlrunkit fleet register-root --fleet-root <directory>
200
215
 
201
216
  Registers one durable fleet coverage root for later no-argument discovery.
217
+ `,
218
+ "fleet inspect-registry": `Usage:
219
+ owlrunkit fleet inspect-registry [--registry <file>]
220
+
221
+ Reads the exact registry bytes, hashes, roots, and membership without mutation.
222
+ `,
223
+ "fleet replace-registry": `Usage:
224
+ owlrunkit fleet replace-registry --request <file> --receipt <file>
225
+ [--registry <file>] [--dry-run]
226
+
227
+ Atomically replaces a registry from an exact preimage and evidence-bound fleet
228
+ membership. Coverage reduction requires explicit exclusion evidence.
229
+ `,
230
+ "fleet rollback-registry": `Usage:
231
+ owlrunkit fleet rollback-registry --receipt <replacement-receipt.json>
232
+ --rollback-receipt <file> [--registry <file>]
233
+
234
+ Restores the exact preimage recorded by a replacement receipt.
202
235
  `,
203
236
  "fleet discover": `Usage:
204
237
  owlrunkit fleet discover [--fleet-root <directory>] [--fleet-manifest <file>]
@@ -230,7 +263,12 @@ Creates the execution and exact writer lease.
230
263
  `,
231
264
  "formal check": `Usage:
232
265
  owlrunkit formal check --workspace <directory> --run-id <id>
233
- --work-item <id> --check-id <id> --cwd <directory> -- <command> [args...]
266
+ --work-item <id> --check-id <id> --envelope <verification-envelope.json>
267
+ owlrunkit formal check --workspace <directory> --run-id <id>
268
+ --work-item <id> --check-id <id> --cwd <directory> -- <built-in-command> [args...]
269
+
270
+ Verification Envelopes admit real project commands only when file, network,
271
+ process, environment, output, timeout, and cleanup constraints are enforced.
234
272
  `,
235
273
  "formal finish": `Usage:
236
274
  owlrunkit formal finish --workspace <directory> --run-id <id>
@@ -299,6 +337,24 @@ profile, preflight, lineage, and result. --request is an advanced legacy path.
299
337
 
300
338
  Shows completed milestones, current state, remaining gates, and the next allowed
301
339
  action without granting any authority.
340
+ `,
341
+ project: `Usage:
342
+ owlrunkit project init --workspace <directory> --definition <project.json>
343
+ owlrunkit project <assign|checkpoint|handoff|decision|capture> --workspace <directory> --request <event.json>
344
+ owlrunkit project status --workspace <directory> [--json]
345
+ owlrunkit project takeover --workspace <directory> --agent <id> [--json]
346
+
347
+ Project status is derived from durable work items, Agent assignments,
348
+ dependencies, decisions, checkpoints, integration gates, and evidence refs.
349
+ No self-reported percentage is accepted.
350
+ `,
351
+ "project status": `Usage:
352
+ owlrunkit project status --workspace <directory> [--json]
353
+ `,
354
+ "project takeover": `Usage:
355
+ owlrunkit project takeover --workspace <directory> --agent <id> [--json]
356
+
357
+ Builds a read-only blank-session recovery packet for one Agent.
302
358
  `,
303
359
  });
304
360
 
@@ -1564,6 +1620,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1564
1620
  "formal",
1565
1621
  "lease",
1566
1622
  "profiles",
1623
+ "project",
1567
1624
  ]).has(command);
1568
1625
  const [action, ...nestedOptions] = nested ? optionValues : [null, ...optionValues];
1569
1626
  const helpRequested = optionValues.includes("--help")
@@ -1598,10 +1655,14 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1598
1655
  ? ["json", "verbose", "history"]
1599
1656
  : command === "init"
1600
1657
  ? ["json"]
1658
+ : command === "project" && new Set(["status", "takeover"]).has(action)
1659
+ ? ["json"]
1601
1660
  : command === "doctor" || command === "adopt" || command === "status"
1602
1661
  ? ["json"]
1603
1662
  : command === "profiles"
1604
1663
  ? ["json", "dry-run", "apply"]
1664
+ : command === "fleet" && action === "replace-registry"
1665
+ ? ["dry-run"]
1605
1666
  : command === "candidate"
1606
1667
  ? ["delivery-v1"]
1607
1668
  : command === "core-successor" && action === "resume"
@@ -1651,19 +1712,50 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
1651
1712
  if (action === "register-root") {
1652
1713
  const registered = await registerFleetCoverageRoot({
1653
1714
  fleetRoot: requireOption(options, "fleet-root"),
1715
+ fleetRegistryPath: options.registry,
1654
1716
  });
1655
1717
  return {
1656
1718
  ...registered,
1657
1719
  exitCode: 0,
1658
1720
  };
1659
1721
  }
1722
+ if (action === "inspect-registry") {
1723
+ return {
1724
+ ...await inspectFleetRegistry({
1725
+ fleetRegistryPath: options.registry,
1726
+ }),
1727
+ exitCode: 0,
1728
+ };
1729
+ }
1730
+ if (action === "replace-registry") {
1731
+ return {
1732
+ ...await replaceFleetRegistry({
1733
+ replacementRequestPath: requireOption(options, "request"),
1734
+ receiptPath: options.receipt,
1735
+ dryRun: options["dry-run"] === true,
1736
+ fleetRegistryPath: options.registry,
1737
+ }),
1738
+ exitCode: 0,
1739
+ };
1740
+ }
1741
+ if (action === "rollback-registry") {
1742
+ return {
1743
+ ...await rollbackFleetRegistry({
1744
+ replacementReceiptPath: requireOption(options, "receipt"),
1745
+ rollbackReceiptPath: requireOption(options, "rollback-receipt"),
1746
+ fleetRegistryPath: options.registry,
1747
+ }),
1748
+ exitCode: 0,
1749
+ };
1750
+ }
1660
1751
  if (action !== "discover") {
1661
- throw new Error("Usage: owlrunkit fleet <register-root|discover> [--fleet-root <directory> | --fleet-manifest <file> | --workspace-root <directory>]");
1752
+ throw new Error("Usage: owlrunkit fleet <register-root|inspect-registry|replace-registry|rollback-registry|discover>");
1662
1753
  }
1663
1754
  const discovered = await discoverFleet({
1664
1755
  fleetRoots: options["fleet-root"] ?? null,
1665
1756
  fleetManifestPath: options["fleet-manifest"] ?? null,
1666
1757
  workspaceRoots: options["workspace-root"] ?? null,
1758
+ fleetRegistryPath: options.registry ?? null,
1667
1759
  });
1668
1760
  return {
1669
1761
  ...discovered,
@@ -2172,10 +2264,30 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
2172
2264
  });
2173
2265
  }
2174
2266
  if (action === "check") {
2267
+ const workItemId = options["work-item"] ?? options["from-lease"] ?? "delivery";
2268
+ if (
2269
+ options["work-item"] !== undefined
2270
+ && options["from-lease"] !== undefined
2271
+ && options["work-item"] !== options["from-lease"]
2272
+ ) {
2273
+ throw new Error("formal check work-item and legacy from-lease must match.");
2274
+ }
2275
+ if (options.envelope !== undefined) {
2276
+ if (commandArgv.length > 0) {
2277
+ throw new Error("formal check accepts either --envelope or an exact built-in command, not both.");
2278
+ }
2279
+ return await recordFormalEnvelopeCheckV1({
2280
+ workspaceRoot: workspace(options),
2281
+ runId: safeRunId(requireOption(options, "run-id")),
2282
+ workItemId,
2283
+ checkId: requireOption(options, "check-id"),
2284
+ envelope: readJson(realpathSync(requireOption(options, "envelope"))),
2285
+ });
2286
+ }
2175
2287
  return recordFormalCheckV1({
2176
2288
  workspaceRoot: workspace(options),
2177
2289
  runId: safeRunId(requireOption(options, "run-id")),
2178
- workItemId: options["from-lease"] ?? "delivery",
2290
+ workItemId,
2179
2291
  checkId: requireOption(options, "check-id"),
2180
2292
  cwd: options.cwd ?? ".",
2181
2293
  commandArgv,
@@ -2292,6 +2404,43 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
2292
2404
  ].join("\n"),
2293
2405
  );
2294
2406
  }
2407
+ if (command === "project") {
2408
+ const root = requireOption(options, "workspace");
2409
+ if (action === "init") {
2410
+ return initializeTeamProjectV1({
2411
+ workspaceRoot: root,
2412
+ definition: readJson(realpathSync(requireOption(options, "definition"))),
2413
+ });
2414
+ }
2415
+ if (new Set(["assign", "checkpoint", "handoff", "decision", "capture"]).has(action)) {
2416
+ return appendTeamProjectEventV1({
2417
+ workspaceRoot: root,
2418
+ event: readJson(realpathSync(requireOption(options, "request"))),
2419
+ });
2420
+ }
2421
+ if (action === "status") {
2422
+ const status = readTeamProjectStatusV1({ workspaceRoot: root });
2423
+ return options.json === true
2424
+ ? status
2425
+ : withHumanOutput(status, formatTeamProjectStatusHumanV1(status));
2426
+ }
2427
+ if (action === "takeover") {
2428
+ const takeover = buildTeamProjectTakeoverV1({
2429
+ workspaceRoot: root,
2430
+ agentId: requireOption(options, "agent"),
2431
+ });
2432
+ return options.json === true
2433
+ ? takeover
2434
+ : withHumanOutput(takeover, [
2435
+ `${takeover.projectId}: ${takeover.objective}`,
2436
+ `Agent: ${takeover.agentId}`,
2437
+ `Responsibility: ${takeover.currentResponsibility?.workItemId ?? "none"}`,
2438
+ `Next: ${takeover.nextAction ?? "none"}`,
2439
+ "",
2440
+ ].join("\n"));
2441
+ }
2442
+ throw new Error("Usage: owlrunkit project <init|assign|checkpoint|handoff|decision|capture|status|takeover> [options]");
2443
+ }
2295
2444
  if (command === "profiles") {
2296
2445
  const root = requireOption(options, "workspace");
2297
2446
  if (action === "detect") {
@@ -2455,7 +2604,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
2455
2604
  return withControlTransaction(root, () => closeout(options));
2456
2605
  }
2457
2606
  if (command === "finish") return finishExecution(options);
2458
- throw new Error("Usage: owlrunkit <init|doctor|adopt|assurance|fleet|profiles|status|formal|candidate|deployment|core-successor|start|verify|repair|offline-export|offline-import|quick-verify|quick-attest|quick-metrics|finish|plan|inspect|lease|delivery|coverage-adopt|resume|verify-plan|resource-preflight|snapshot|visual-smoke|finalize|ready-for-commit|closeout> [options]");
2607
+ throw new Error("Usage: owlrunkit <init|doctor|adopt|assurance|fleet|profiles|project|status|formal|candidate|deployment|core-successor|start|verify|repair|offline-export|offline-import|quick-verify|quick-attest|quick-metrics|finish|plan|inspect|lease|delivery|coverage-adopt|resume|verify-plan|resource-preflight|snapshot|visual-smoke|finalize|ready-for-commit|closeout> [options]");
2459
2608
  } catch (error) {
2460
2609
  return {
2461
2610
  status: "invalid_input",