project-tiny-context-harness 0.8.12 → 0.8.16

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 (174) hide show
  1. package/README.md +62 -28
  2. package/assets/README.md +98 -45
  3. package/assets/README.zh-CN.md +68 -32
  4. package/assets/agents/AGENTS_CORE.md +10 -6
  5. package/assets/agents/long-task-implementation.toml +1 -1
  6. package/assets/skills/context_development_engineer/SKILL.md +9 -1
  7. package/assets/skills/design-resource-authoring/SKILL.md +7 -5
  8. package/assets/skills/design-resource-authoring/references/downstream-handoff.md +2 -2
  9. package/assets/skills/design-resource-authoring/references/open-design-provider.md +22 -2
  10. package/assets/skills/design-resource-authoring/references/recovery-and-writeback.md +199 -0
  11. package/assets/skills/design-resource-authoring/references/resource-selection.md +2 -2
  12. package/assets/skills/long-task-workflow/SKILL.md +14 -12
  13. package/assets/skills/long-task-workflow/agents/openai.yaml +1 -1
  14. package/assets/skills/long-task-workflow/references/authority-lifecycle.md +1 -1
  15. package/assets/skills/long-task-workflow/references/contract-authoring.md +27 -22
  16. package/assets/skills/long-task-workflow/references/evidence-design.md +49 -32
  17. package/assets/skills/long-task-workflow/references/source-authoring.md +2 -2
  18. package/dist/commands/design-resource-recovery.d.ts +1 -0
  19. package/dist/commands/design-resource-recovery.js +152 -0
  20. package/dist/commands/design-resource.js +20 -1
  21. package/dist/commands/index.js +1 -1
  22. package/dist/commands/long-task-revision.js +4 -4
  23. package/dist/lib/design-resource-reconciliation-codec.d.ts +2 -0
  24. package/dist/lib/design-resource-reconciliation-codec.js +174 -0
  25. package/dist/lib/design-resource-reconciliation-types.d.ts +62 -0
  26. package/dist/lib/design-resource-reconciliation-types.js +1 -0
  27. package/dist/lib/design-resource-reconciliation.d.ts +3 -0
  28. package/dist/lib/design-resource-reconciliation.js +224 -0
  29. package/dist/lib/design-resource-recovery-authority-policy.d.ts +8 -0
  30. package/dist/lib/design-resource-recovery-authority-policy.js +123 -0
  31. package/dist/lib/design-resource-recovery-catalog-resources.d.ts +6 -0
  32. package/dist/lib/design-resource-recovery-catalog-resources.js +121 -0
  33. package/dist/lib/design-resource-recovery-catalog-shape.d.ts +3 -0
  34. package/dist/lib/design-resource-recovery-catalog-shape.js +104 -0
  35. package/dist/lib/design-resource-recovery-catalog.d.ts +9 -0
  36. package/dist/lib/design-resource-recovery-catalog.js +103 -0
  37. package/dist/lib/design-resource-recovery-cleanup.d.ts +2 -0
  38. package/dist/lib/design-resource-recovery-cleanup.js +11 -0
  39. package/dist/lib/design-resource-recovery-codec-primitives.d.ts +19 -0
  40. package/dist/lib/design-resource-recovery-codec-primitives.js +92 -0
  41. package/dist/lib/design-resource-recovery-codec.d.ts +4 -0
  42. package/dist/lib/design-resource-recovery-codec.js +93 -0
  43. package/dist/lib/design-resource-recovery-current.d.ts +9 -0
  44. package/dist/lib/design-resource-recovery-current.js +41 -0
  45. package/dist/lib/design-resource-recovery-delta-policy.d.ts +5 -0
  46. package/dist/lib/design-resource-recovery-delta-policy.js +108 -0
  47. package/dist/lib/design-resource-recovery-files.d.ts +37 -0
  48. package/dist/lib/design-resource-recovery-files.js +204 -0
  49. package/dist/lib/design-resource-recovery-final-disposition-shape.d.ts +2 -0
  50. package/dist/lib/design-resource-recovery-final-disposition-shape.js +47 -0
  51. package/dist/lib/design-resource-recovery-patch-types.d.ts +54 -0
  52. package/dist/lib/design-resource-recovery-patch-types.js +1 -0
  53. package/dist/lib/design-resource-recovery-replay.d.ts +7 -0
  54. package/dist/lib/design-resource-recovery-replay.js +112 -0
  55. package/dist/lib/design-resource-recovery-repository-bindings.d.ts +9 -0
  56. package/dist/lib/design-resource-recovery-repository-bindings.js +49 -0
  57. package/dist/lib/design-resource-recovery-schema.d.ts +5 -0
  58. package/dist/lib/design-resource-recovery-schema.js +5 -0
  59. package/dist/lib/design-resource-recovery-shape.d.ts +10 -0
  60. package/dist/lib/design-resource-recovery-shape.js +184 -0
  61. package/dist/lib/design-resource-recovery-source-authority.d.ts +5 -0
  62. package/dist/lib/design-resource-recovery-source-authority.js +219 -0
  63. package/dist/lib/design-resource-recovery-text.d.ts +17 -0
  64. package/dist/lib/design-resource-recovery-text.js +186 -0
  65. package/dist/lib/design-resource-recovery-types.d.ts +197 -0
  66. package/dist/lib/design-resource-recovery-types.js +1 -0
  67. package/dist/lib/design-resource-recovery-writeback-policy.d.ts +5 -0
  68. package/dist/lib/design-resource-recovery-writeback-policy.js +326 -0
  69. package/dist/lib/design-resource-recovery-writeback-shape.d.ts +6 -0
  70. package/dist/lib/design-resource-recovery-writeback-shape.js +155 -0
  71. package/dist/lib/design-resource-recovery.d.ts +44 -0
  72. package/dist/lib/design-resource-recovery.js +216 -0
  73. package/dist/lib/long-task-acceptance-shape.js +8 -2
  74. package/dist/lib/long-task-activation-validation.js +13 -3
  75. package/dist/lib/long-task-admitted-observation-records.d.ts +25 -0
  76. package/dist/lib/long-task-admitted-observation-records.js +40 -0
  77. package/dist/lib/long-task-admitted-observation.d.ts +93 -0
  78. package/dist/lib/long-task-admitted-observation.js +201 -0
  79. package/dist/lib/long-task-artifacts.d.ts +1 -0
  80. package/dist/lib/long-task-artifacts.js +1 -0
  81. package/dist/lib/long-task-authority-material-diff.js +1 -0
  82. package/dist/lib/long-task-authority-policy.d.ts +2 -0
  83. package/dist/lib/long-task-authority-policy.js +2 -0
  84. package/dist/lib/long-task-authority-revision-brief.js +3 -1
  85. package/dist/lib/long-task-authority-revision-details.js +3 -1
  86. package/dist/lib/long-task-authority-revision-summary.js +6 -2
  87. package/dist/lib/long-task-authority-revision-types.d.ts +2 -0
  88. package/dist/lib/long-task-authority.js +2 -1
  89. package/dist/lib/long-task-check-execution-policy.js +5 -0
  90. package/dist/lib/long-task-check-runner.d.ts +2 -2
  91. package/dist/lib/long-task-check-runner.js +440 -23
  92. package/dist/lib/long-task-claims.d.ts +18 -0
  93. package/dist/lib/long-task-claims.js +35 -0
  94. package/dist/lib/long-task-compact-structure-targets.js +1 -0
  95. package/dist/lib/long-task-conformance-policy.js +3 -1
  96. package/dist/lib/long-task-counterfactual-claim-policy.d.ts +3 -4
  97. package/dist/lib/long-task-counterfactual-claim-policy.js +11 -3
  98. package/dist/lib/long-task-counterfactual-sandbox.js +1 -0
  99. package/dist/lib/long-task-counterfactual-types.d.ts +2 -0
  100. package/dist/lib/long-task-delivery-compiler.js +2 -0
  101. package/dist/lib/long-task-design-resource-method-binding.js +8 -2
  102. package/dist/lib/long-task-evidence-capability-policy.d.ts +3 -2
  103. package/dist/lib/long-task-evidence-capability-policy.js +114 -8
  104. package/dist/lib/long-task-evidence-capability-runtime.d.ts +5 -2
  105. package/dist/lib/long-task-evidence-capability-runtime.js +149 -5
  106. package/dist/lib/long-task-evidence-sensitivity-policy.d.ts +9 -0
  107. package/dist/lib/long-task-evidence-sensitivity-policy.js +31 -1
  108. package/dist/lib/long-task-evidence-v2.d.ts +4 -4
  109. package/dist/lib/long-task-evidence-v2.js +321 -43
  110. package/dist/lib/long-task-exact-comparison.d.ts +16 -0
  111. package/dist/lib/long-task-exact-comparison.js +28 -0
  112. package/dist/lib/long-task-execution-observation.d.ts +24 -0
  113. package/dist/lib/long-task-execution-observation.js +332 -0
  114. package/dist/lib/long-task-explain-acceptance-link.d.ts +4 -0
  115. package/dist/lib/long-task-explain-acceptance-link.js +4 -0
  116. package/dist/lib/long-task-explain-claim-links.d.ts +2 -0
  117. package/dist/lib/long-task-explain-claim-links.js +2 -0
  118. package/dist/lib/long-task-explain-source-links.d.ts +6 -0
  119. package/dist/lib/long-task-final-v2.js +7 -4
  120. package/dist/lib/long-task-git.d.ts +26 -0
  121. package/dist/lib/long-task-git.js +130 -0
  122. package/dist/lib/long-task-json-pointer-observation.d.ts +59 -0
  123. package/dist/lib/long-task-json-pointer-observation.js +184 -0
  124. package/dist/lib/long-task-observation-artifact.d.ts +8 -0
  125. package/dist/lib/long-task-observation-artifact.js +58 -0
  126. package/dist/lib/long-task-observation-authority.d.ts +17 -0
  127. package/dist/lib/long-task-observation-authority.js +348 -0
  128. package/dist/lib/long-task-paths.js +2 -2
  129. package/dist/lib/long-task-process-observation.d.ts +6 -0
  130. package/dist/lib/long-task-process-observation.js +117 -0
  131. package/dist/lib/long-task-process-runtime-closure.d.ts +13 -0
  132. package/dist/lib/long-task-process-runtime-closure.js +207 -0
  133. package/dist/lib/long-task-progress.js +2 -0
  134. package/dist/lib/long-task-protected-files.d.ts +1 -1
  135. package/dist/lib/long-task-protected-files.js +1 -30
  136. package/dist/lib/long-task-risk.js +8 -3
  137. package/dist/lib/long-task-root-shape.js +10 -1
  138. package/dist/lib/long-task-runner-environment.d.ts +1 -1
  139. package/dist/lib/long-task-runner-environment.js +4 -4
  140. package/dist/lib/long-task-runner-freeze.d.ts +4 -2
  141. package/dist/lib/long-task-runner-freeze.js +128 -28
  142. package/dist/lib/long-task-runtime-types.d.ts +111 -0
  143. package/dist/lib/long-task-scoped-binding.d.ts +13 -0
  144. package/dist/lib/long-task-scoped-binding.js +11 -0
  145. package/dist/lib/long-task-semantic-contract-types.d.ts +1 -0
  146. package/dist/lib/long-task-semantic-fact-contract-closure.js +3 -3
  147. package/dist/lib/long-task-semantic-fact-evidence.d.ts +3 -3
  148. package/dist/lib/long-task-semantic-fact-evidence.js +75 -58
  149. package/dist/lib/long-task-source-claim-validation.js +4 -1
  150. package/dist/lib/long-task-source-target-continuity.d.ts +2 -2
  151. package/dist/lib/long-task-source-target-continuity.js +35 -2
  152. package/dist/lib/long-task-source-target-index.d.ts +3 -0
  153. package/dist/lib/long-task-source-target-index.js +19 -0
  154. package/dist/lib/long-task-stage-policy.js +10 -4
  155. package/dist/lib/long-task-static-observation-freeze.d.ts +83 -0
  156. package/dist/lib/long-task-static-observation-freeze.js +428 -0
  157. package/dist/lib/long-task-status-projection.js +5 -1
  158. package/dist/lib/long-task-target-policy.d.ts +1 -1
  159. package/dist/lib/long-task-target-policy.js +3 -0
  160. package/dist/lib/long-task-verifier-authority.js +2 -0
  161. package/dist/lib/long-task-verifier-dependency-closure.js +7 -3
  162. package/dist/lib/long-task-verifier-v2.js +70 -9
  163. package/dist/lib/long-task-workspace-manifest.d.ts +8 -0
  164. package/dist/lib/long-task-workspace-manifest.js +173 -0
  165. package/dist/lib/long-task-workspace-snapshot.d.ts +8 -0
  166. package/dist/lib/long-task-workspace-snapshot.js +176 -0
  167. package/dist/lib/long-task-workspace.d.ts +5 -26
  168. package/dist/lib/long-task-workspace.js +4 -444
  169. package/dist/lib/repository-path-safety.d.ts +14 -0
  170. package/dist/lib/repository-path-safety.js +126 -0
  171. package/dist/long-task-hook.js +12 -3
  172. package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +53 -8
  173. package/migrations/README.md +77 -0
  174. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { outcomeResultExternallyBlocked } from "./long-task-claims.js";
1
2
  import { progressRecordFresh } from "./long-task-progress.js";
2
3
  export function projectDeliveryStatus(input) {
3
4
  const outcomes = projectOutcomes(input);
@@ -46,7 +47,8 @@ function projectOutcomes(input) {
46
47
  });
47
48
  outcomes[outcome.key] = states.includes("progress_failing")
48
49
  ? "progress_failing"
49
- : states.includes("blocked_external")
50
+ : outcomeResultExternallyBlocked(input.compiled, outcome.key) ||
51
+ states.includes("blocked_external")
50
52
  ? "blocked_external"
51
53
  : states.includes("progress_stale")
52
54
  ? "progress_stale"
@@ -99,7 +101,9 @@ function projectFindings(input) {
99
101
  function readyOutcomes(compiled, outcomes, stages) {
100
102
  return compiled.outcomes
101
103
  .filter((outcome) => stages[outcome.stage] !== "locked" &&
104
+ stages[outcome.stage] !== "blocked_external" &&
102
105
  stages[outcome.stage] !== "progress_passing" &&
106
+ outcomes[outcome.key] !== "blocked_external" &&
103
107
  outcomes[outcome.key] !== "progress_passing" &&
104
108
  outcome.depends_on.every((dependency) => outcomes[dependency] === "progress_passing"))
105
109
  .map((outcome) => outcome.key);
@@ -1,4 +1,4 @@
1
- import type { CompiledClaimsV2 } from "./long-task-claims.js";
1
+ import { type CompiledClaimsV2 } from "./long-task-claims.js";
2
2
  import type { DeliveryContractV2 } from "./long-task-delivery-types.js";
3
3
  type Reporter = (message: string) => void;
4
4
  export declare function validateExecutionTargets(contract: DeliveryContractV2, report?: Reporter): void;
@@ -1,3 +1,4 @@
1
+ import { outcomeResultExternallyBlocked, } from "./long-task-claims.js";
1
2
  const FAMILY_CAPABILITY = {
2
3
  browser: "browser-runtime",
3
4
  native: "native-runtime",
@@ -94,6 +95,8 @@ function validateCriticalPathTargets(contract, requiredTargetRefs, report) {
94
95
  const outcome = contract.outcomes.find((candidate) => candidate.key === outcomeKey);
95
96
  if (!outcome)
96
97
  continue;
98
+ if (outcomeResultExternallyBlocked(contract, outcome.key))
99
+ continue;
97
100
  for (const targetRef of requiredTargetRefs)
98
101
  if (!outcome.acceptance.checks.some((check) => provesSuccess(check, targetRef)))
99
102
  issue(report, "critical_path_required_target_proof_missing", `${outcome.key}:${targetRef}`);
@@ -37,6 +37,8 @@ function changedVerifierFiles(previous, next) {
37
37
  ...Object.keys(next.bundle_files),
38
38
  ]);
39
39
  const changed = [...files].filter((file) => previous.bundle_files[file] !== next.bundle_files[file]);
40
+ if (previous.bundle_sha256 !== next.bundle_sha256)
41
+ changed.push("<bundle>");
40
42
  if (previous.schema_sha256 !== next.schema_sha256)
41
43
  changed.push("<schema>");
42
44
  if (previous.hook_sha256 !== next.hook_sha256)
@@ -167,11 +167,15 @@ function localSpecifierBase(importer, specifier) {
167
167
  const base = specifier.startsWith("/")
168
168
  ? specifier.slice(1)
169
169
  : path.posix.normalize(path.posix.join(path.posix.dirname(importer), specifier));
170
- return base === ".." || base.startsWith("../") ? null : base;
170
+ const canonicalBase = base.replace(/\/+$/u, "") || ".";
171
+ return canonicalBase === ".." || canonicalBase.startsWith("../")
172
+ ? null
173
+ : canonicalBase;
171
174
  }
172
175
  function runtimePatternOwns(base, pattern) {
173
- return (matchesRepoPattern(base, pattern) ||
174
- classifyRepositoryPatternOverlap(`${base}/**`, pattern).status ===
176
+ const directoryPattern = base === "." ? "**" : `${base}/**`;
177
+ return ((base !== "." && matchesRepoPattern(base, pattern)) ||
178
+ classifyRepositoryPatternOverlap(directoryPattern, pattern).status ===
175
179
  "proven_overlap");
176
180
  }
177
181
  function sortRecord(value) {
@@ -2,6 +2,7 @@ import { deliveryCompileFreshness } from "./long-task-freshness.js";
2
2
  import { assertVerifierAuthorityCurrent } from "./long-task-freshness.js";
3
3
  import { enrichCheckResultFindings, enrichFinding, } from "./long-task-finding-context.js";
4
4
  import { executeCheckRunner } from "./long-task-check-runner.js";
5
+ import { prepareExecutionObservationUniverse } from "./long-task-execution-observation.js";
5
6
  import { evaluateCheckEvidence, evaluateGlobalCounterfactuals, evaluateOutcomeCounterfactuals, } from "./long-task-evidence-v2.js";
6
7
  import { matchesRepoPattern } from "./long-task-paths.js";
7
8
  import { activeAuthorityIdentityMatches, loadActiveLongTaskAuthority, writeProgressRecord, } from "./long-task-state.js";
@@ -85,6 +86,12 @@ async function resolved(workdir) {
85
86
  return (await import("node:path")).default.resolve(workdir);
86
87
  }
87
88
  export async function runDeliveryChecks(compiled, snapshot, checks, includeCounterfactuals, finalGate = false) {
89
+ const observationAuthorityPaths = [
90
+ compiled.contract_file,
91
+ ...Object.keys(compiled.contract_files),
92
+ ...Object.keys(compiled.source_hashes),
93
+ ...compiled.context_snapshot.files,
94
+ ];
88
95
  const findings = await preRunFindings(compiled, snapshot.manifest);
89
96
  if (finalGate)
90
97
  findings.push(...finalPathFindings(compiled, snapshot.manifest));
@@ -95,17 +102,46 @@ export async function runDeliveryChecks(compiled, snapshot, checks, includeCount
95
102
  findings: findings.map((finding) => enrichFinding(compiled, finding)),
96
103
  };
97
104
  const rawExecutions = new Map();
105
+ const completeChecks = allCompiledChecks(compiled);
106
+ const selectedExecutionGroups = rawExecutionGroups(checks);
107
+ const completeExecutionGroups = selectedExecutionGroups.map((group) => completeChecks.filter((check) => check.raw_execution_identity === group[0].raw_execution_identity));
108
+ const preparedExecutionGroups = await prepareExecutionObservationUniverse({
109
+ groups: completeExecutionGroups,
110
+ snapshot_root: snapshot.root,
111
+ workspace_manifest: snapshot.manifest,
112
+ protected_authority_paths: observationAuthorityPaths,
113
+ });
114
+ try {
115
+ const pending = [];
116
+ for (const [index, group] of selectedExecutionGroups.entries()) {
117
+ const prepared = preparedExecutionGroups[index];
118
+ if (!prepared)
119
+ throw new Error("raw_execution_group_prepare_missing");
120
+ pending.push({
121
+ raw_execution_identity: group[0].raw_execution_identity,
122
+ raw: await executeCheckRunner(group[0], prepared.execution_root, prepared.runner_context),
123
+ prepared,
124
+ });
125
+ }
126
+ const finalized = await Promise.all(pending.map(async (entry) => ({
127
+ raw_execution_identity: entry.raw_execution_identity,
128
+ raw: await entry.prepared.finalize(entry.raw),
129
+ })));
130
+ for (const entry of finalized)
131
+ rawExecutions.set(entry.raw_execution_identity, entry.raw);
132
+ }
133
+ finally {
134
+ await Promise.all(preparedExecutionGroups.map((prepared) => prepared.dispose()));
135
+ }
98
136
  const mainCheckResults = [];
99
137
  for (const check of checks) {
100
- let raw = rawExecutions.get(check.raw_execution_identity);
101
- if (!raw) {
102
- raw = await executeCheckRunner(check, snapshot.root);
103
- rawExecutions.set(check.raw_execution_identity, raw);
104
- }
138
+ const raw = rawExecutions.get(check.raw_execution_identity);
139
+ if (!raw)
140
+ throw new Error("raw_execution_group_result_missing");
105
141
  const outcome = check.outcome_key
106
142
  ? compiled.outcomes.find((item) => item.key === check.outcome_key)
107
143
  : undefined;
108
- mainCheckResults.push(await evaluateCheckEvidence(check, raw, snapshot.root, outcome));
144
+ mainCheckResults.push(await evaluateCheckEvidence(check, raw, snapshot.root, outcome, observationAuthorityPaths));
109
145
  }
110
146
  const counterfactualFindings = [];
111
147
  if (includeCounterfactuals) {
@@ -122,7 +158,7 @@ export async function runDeliveryChecks(compiled, snapshot, checks, includeCount
122
158
  passingCheckIds.has(check.internal_id))
123
159
  .map((check) => check.key));
124
160
  if (selectedGlobalCheckKeys.size)
125
- counterfactualFindings.push(...(await evaluateGlobalCounterfactuals(compiled, snapshot.root, selectedGlobalCheckKeys, snapshot.manifest)));
161
+ counterfactualFindings.push(...(await evaluateGlobalCounterfactuals(compiled, snapshot.root, selectedGlobalCheckKeys, snapshot.manifest, mainCheckResults, rawExecutions, completeChecks)));
126
162
  const outcomeKeys = new Set(checks
127
163
  .map((check) => check.outcome_key)
128
164
  .filter((key) => Boolean(key)));
@@ -137,7 +173,7 @@ export async function runDeliveryChecks(compiled, snapshot, checks, includeCount
137
173
  ...outcome.acceptance,
138
174
  counterfactual_controls: outcome.acceptance.counterfactual_controls.filter((control) => selectedKeys.has(control.check_key)),
139
175
  },
140
- }, snapshot.root, snapshot.manifest, compiled.context_snapshot.files)));
176
+ }, snapshot.root, snapshot.manifest, observationAuthorityPaths, mainCheckResults, rawExecutions, completeChecks)));
141
177
  }
142
178
  }
143
179
  const unassignedCounterfactuals = counterfactualFindings.filter((finding) => !mainCheckResults.some((result) => findingBelongsToCheck(finding, result)));
@@ -154,6 +190,17 @@ export async function runDeliveryChecks(compiled, snapshot, checks, includeCount
154
190
  findings: findings.map((finding) => enrichFinding(compiled, finding)),
155
191
  };
156
192
  }
193
+ function rawExecutionGroups(checks) {
194
+ const groups = new Map();
195
+ for (const check of checks) {
196
+ const group = groups.get(check.raw_execution_identity);
197
+ if (group)
198
+ group.push(check);
199
+ else
200
+ groups.set(check.raw_execution_identity, [check]);
201
+ }
202
+ return [...groups.values()];
203
+ }
157
204
  function applyCounterfactualFindings(checkResults, findings, unassignedFinding) {
158
205
  return checkResults.map((result, index) => {
159
206
  const projected = findings.filter((finding) => findingBelongsToCheck(finding, result));
@@ -200,7 +247,9 @@ function finalPathFindings(compiled, manifest) {
200
247
  for (const outcome of compiled.outcomes)
201
248
  for (const binding of outcome.technical.bindings)
202
249
  if (binding.existence === "planned" &&
203
- !plannedBindingRequiredPatterns(binding).every((pattern) => manifest.files.some((file) => matchesRepoPattern(file.path, pattern))))
250
+ !plannedBindingRequiredPatterns(binding).every((pattern) => manifest.files.some((file) => matchesRepoPattern(file.path, pattern))) &&
251
+ !outcome.acceptance.checks.some((check) => (check.observation_authorities ?? []).some((authority) => authority.authority === "package_static_json_exact" &&
252
+ authority.carrier_refs.some((carrier) => carrier.binding_ref === `${outcome.key}.${binding.key}`))))
204
253
  findings.push({
205
254
  code: "binding_missing",
206
255
  outcome_key: outcome.key,
@@ -212,6 +261,18 @@ function finalPathFindings(compiled, manifest) {
212
261
  expected: binding.carrier_paths,
213
262
  next_action: "Implement the declared binding and rerun verification.",
214
263
  });
264
+ for (const check of allCompiledChecks(compiled))
265
+ for (const runtimePath of check.process_runtime_closure
266
+ ?.allowed_runtime_files ?? [])
267
+ if (!manifest.files.some((file) => file.path === runtimePath))
268
+ findings.push({
269
+ code: "process_runtime_input_missing",
270
+ outcome_key: check.outcome_key,
271
+ check_key: check.key,
272
+ message: `Declared process runtime input did not exist in the Final Gate candidate: ${runtimePath}`,
273
+ expected: runtimePath,
274
+ next_action: "Materialize the Source-backed product root and every declared production runtime input, then rerun Final Gate.",
275
+ });
215
276
  return findings;
216
277
  }
217
278
  function plannedBindingRequiredPatterns(binding) {
@@ -0,0 +1,8 @@
1
+ import type { WorkspaceFingerprintV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
2
+ export declare function captureWorkspaceFingerprint(rootInput: string, excludedPrefixes?: string[]): Promise<WorkspaceFingerprintV2>;
3
+ export declare function captureWorkspaceManifest(rootInput: string, workdirInput: string, _copyRoot?: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceManifestV2>;
4
+ export declare function changedWorkspacePaths(baseline: WorkspaceManifestV2, current: WorkspaceManifestV2): string[];
5
+ export declare function changedWorkspacePathsFromHead(rootInput: string, workdirInput: string, additionalExcludedWorkdirs?: string[]): Promise<string[]>;
6
+ export declare function workspaceFingerprintExcludedPrefixes(root: string, workdirs: string[]): string[];
7
+ export declare function workspaceSnapshotExcludedPrefixes(root: string, workdirs: string[]): string[];
8
+ export declare function workspacePathExcluded(relative: string, excluded: string[]): boolean;
@@ -0,0 +1,173 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { gitBuffer, gitBufferInput, gitOutput, repoRelative, splitGitZero, } from "./long-task-git.js";
4
+ import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
5
+ export async function captureWorkspaceFingerprint(rootInput, excludedPrefixes = []) {
6
+ const root = path.resolve(rootInput);
7
+ const indexTree = await gitOutput(root, ["write-tree"]);
8
+ const [head, headTree, staged, unstaged, statusBytes, untracked] = await Promise.all([
9
+ gitOutput(root, ["rev-parse", "HEAD"]),
10
+ gitOutput(root, ["rev-parse", "HEAD^{tree}"]),
11
+ gitBuffer(root, scopedDiffArgs(["diff", "--cached", "--binary", "--no-ext-diff"], excludedPrefixes)),
12
+ gitBuffer(root, scopedDiffArgs(["diff", "--binary", "--no-ext-diff"], excludedPrefixes)),
13
+ Promise.all([
14
+ gitBuffer(root, scopedDiffArgs(["diff", "--cached", "--raw", "-z", "-M"], excludedPrefixes)),
15
+ gitBuffer(root, scopedDiffArgs(["diff", "--raw", "-z", "-M"], excludedPrefixes)),
16
+ ]).then((rows) => Buffer.concat(rows)),
17
+ untrackedIdentity(root, excludedPrefixes),
18
+ ]);
19
+ const unsigned = {
20
+ head,
21
+ head_tree: headTree,
22
+ index_tree: indexTree,
23
+ staged_diff_sha256: sha256Hex(staged),
24
+ unstaged_diff_sha256: sha256Hex(unstaged),
25
+ untracked_sha256: untracked,
26
+ status_sha256: sha256Hex(statusBytes),
27
+ };
28
+ return {
29
+ ...unsigned,
30
+ identity: sha256Hex(canonicalValueJson(unsigned)),
31
+ };
32
+ }
33
+ export async function captureWorkspaceManifest(rootInput, workdirInput, _copyRoot, additionalExcludedWorkdirs = []) {
34
+ const root = path.resolve(rootInput);
35
+ const workdir = path.resolve(workdirInput);
36
+ const workdirRelative = repoRelative(root, workdir);
37
+ if (!workdirRelative)
38
+ throw new Error("long_task_workdir_must_not_be_repository_root");
39
+ const excluded = workspaceFingerprintExcludedPrefixes(root, [
40
+ workdir,
41
+ ...additionalExcludedWorkdirs,
42
+ ]);
43
+ const fingerprint = await captureWorkspaceFingerprint(root, excluded);
44
+ const [indexBytes, modifiedBytes, untrackedBytes] = await Promise.all([
45
+ gitBuffer(root, ["ls-files", "--stage", "-z"]),
46
+ gitBuffer(root, ["diff", "--name-only", "-z"]),
47
+ gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]),
48
+ ]);
49
+ const files = new Map();
50
+ for (const record of splitGitZero(indexBytes)) {
51
+ const tab = record.indexOf("\t");
52
+ if (tab < 0)
53
+ continue;
54
+ const [modeText, objectId, stage] = record.slice(0, tab).split(" ");
55
+ const relative = record.slice(tab + 1).replace(/\\/gu, "/");
56
+ if (stage !== "0" || workspacePathExcluded(relative, excluded))
57
+ continue;
58
+ files.set(relative, {
59
+ path: relative,
60
+ mode: Number.parseInt(modeText, 8),
61
+ size: 0,
62
+ sha256: `git:${objectId}`,
63
+ });
64
+ }
65
+ const overlays = new Set([
66
+ ...splitGitZero(modifiedBytes),
67
+ ...splitGitZero(untrackedBytes),
68
+ ]);
69
+ const overlayNames = [...overlays]
70
+ .map((raw) => raw.replace(/\\/gu, "/"))
71
+ .filter((relative) => !workspacePathExcluded(relative, excluded));
72
+ const overlayInfo = new Map(await Promise.all(overlayNames.map(async (relative) => [
73
+ relative,
74
+ await stat(path.join(root, ...relative.split("/")), {
75
+ bigint: true,
76
+ }).catch(() => null),
77
+ ])));
78
+ const overlayHashes = await gitObjectIds(root, overlayNames.filter((relative) => overlayInfo.get(relative)?.isFile()));
79
+ for (const relative of overlayNames) {
80
+ const absolute = path.join(root, ...relative.split("/"));
81
+ const info = overlayInfo.get(relative);
82
+ if (!info?.isFile()) {
83
+ files.delete(relative);
84
+ continue;
85
+ }
86
+ const bytes = await readFile(absolute);
87
+ files.set(relative, {
88
+ path: relative,
89
+ mode: gitFileMode(Number(info.mode)),
90
+ size: bytes.length,
91
+ sha256: `git:${overlayHashes.get(relative)}`,
92
+ });
93
+ }
94
+ return {
95
+ repository_root: root,
96
+ git_head: fingerprint.head,
97
+ files: [...files.values()].sort((a, b) => a.path.localeCompare(b.path)),
98
+ fingerprint,
99
+ snapshot_sha256: fingerprint.identity,
100
+ };
101
+ }
102
+ export function changedWorkspacePaths(baseline, current) {
103
+ const before = new Map(baseline.files.map((file) => [file.path, `${file.mode}:${file.sha256}`]));
104
+ const after = new Map(current.files.map((file) => [file.path, `${file.mode}:${file.sha256}`]));
105
+ return [...new Set([...before.keys(), ...after.keys()])]
106
+ .filter((file) => before.get(file) !== after.get(file))
107
+ .sort();
108
+ }
109
+ export async function changedWorkspacePathsFromHead(rootInput, workdirInput, additionalExcludedWorkdirs = []) {
110
+ const root = path.resolve(rootInput);
111
+ const workdir = path.resolve(workdirInput);
112
+ const excluded = workspaceFingerprintExcludedPrefixes(root, [
113
+ workdir,
114
+ ...additionalExcludedWorkdirs,
115
+ ]);
116
+ const [trackedBytes, untrackedBytes] = await Promise.all([
117
+ gitBuffer(root, scopedDiffArgs(["diff", "--name-only", "--no-renames", "--no-ext-diff", "-z", "HEAD"], excluded)),
118
+ gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]),
119
+ ]);
120
+ return [
121
+ ...new Set([...splitGitZero(trackedBytes), ...splitGitZero(untrackedBytes)]
122
+ .map((raw) => raw.replace(/\\/gu, "/"))
123
+ .filter((relative) => !workspacePathExcluded(relative, excluded))),
124
+ ].sort();
125
+ }
126
+ export function workspaceFingerprintExcludedPrefixes(root, workdirs) {
127
+ return [
128
+ ...workspaceSnapshotExcludedPrefixes(root, workdirs),
129
+ "project_context",
130
+ ].filter(Boolean);
131
+ }
132
+ export function workspaceSnapshotExcludedPrefixes(root, workdirs) {
133
+ return [
134
+ ...workdirs.map((workdir) => repoRelative(root, path.resolve(workdir))),
135
+ "tmp/ty-context/long-task-runs",
136
+ ].filter(Boolean);
137
+ }
138
+ export function workspacePathExcluded(relative, excluded) {
139
+ const normalized = relative.replace(/\\/gu, "/");
140
+ return (normalized.split("/").includes("node_modules") ||
141
+ excluded.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`)));
142
+ }
143
+ async function untrackedIdentity(root, excluded) {
144
+ const names = splitGitZero(await gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]))
145
+ .map((name) => name.replace(/\\/gu, "/"))
146
+ .filter((name) => !workspacePathExcluded(name, excluded))
147
+ .sort();
148
+ const hashes = await gitObjectIds(root, names);
149
+ const rows = names.map((name) => [
150
+ name,
151
+ hashes.get(name) ?? "missing",
152
+ ]);
153
+ return sha256Hex(canonicalValueJson(rows));
154
+ }
155
+ async function gitObjectIds(root, names) {
156
+ if (!names.length)
157
+ return new Map();
158
+ const output = await gitBufferInput(root, ["hash-object", "--stdin-paths"], Buffer.from(`${names.join("\n")}\n`, "utf8"));
159
+ const ids = output.toString("utf8").trim().split(/\r?\n/u);
160
+ return new Map(names.map((name, index) => [name, ids[index]]));
161
+ }
162
+ function scopedDiffArgs(base, excluded) {
163
+ return [
164
+ ...base,
165
+ "--",
166
+ ".",
167
+ ...excluded.map((prefix) => `:(exclude)${prefix}/**`),
168
+ ":(exclude)**/node_modules/**",
169
+ ];
170
+ }
171
+ function gitFileMode(mode) {
172
+ return mode & 0o111 ? 0o100755 : 0o100644;
173
+ }
@@ -0,0 +1,8 @@
1
+ import type { WorkspaceManifestV2 } from "./long-task-delivery-types.js";
2
+ export interface WorkspaceSnapshotV2 {
3
+ root: string;
4
+ manifest: WorkspaceManifestV2;
5
+ preparation_ms: number;
6
+ dispose(): Promise<void>;
7
+ }
8
+ export declare function createWorkspaceSnapshot(rootInput: string, workdirInput: string, label: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceSnapshotV2>;
@@ -0,0 +1,176 @@
1
+ import { copyFile, mkdir, mkdtemp, readdir, rm, stat, symlink, } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { GitCommandError, gitBuffer, gitEffectiveConfigGet, gitVoid, repoRelative, splitGitZero, } from "./long-task-git.js";
5
+ import { captureWorkspaceFingerprint, captureWorkspaceManifest, workspaceFingerprintExcludedPrefixes, workspacePathExcluded, workspaceSnapshotExcludedPrefixes, } from "./long-task-workspace-manifest.js";
6
+ const MAX_CHECKOUT_INDEX_ATTEMPTS = 2;
7
+ export async function createWorkspaceSnapshot(rootInput, workdirInput, label, additionalExcludedWorkdirs = []) {
8
+ const started = performance.now();
9
+ const root = path.resolve(rootInput);
10
+ const workdir = path.resolve(workdirInput);
11
+ const fingerprintExcluded = workspaceFingerprintExcludedPrefixes(root, [
12
+ workdir,
13
+ ...additionalExcludedWorkdirs,
14
+ ]);
15
+ const snapshotExcluded = workspaceSnapshotExcludedPrefixes(root, [
16
+ workdir,
17
+ ...additionalExcludedWorkdirs,
18
+ ]);
19
+ const manifest = await captureWorkspaceManifest(root, workdir, undefined, additionalExcludedWorkdirs);
20
+ const before = manifest.fingerprint;
21
+ const temporary = await checkoutIndexIntoFreshRoot(root, label, before.identity, fingerprintExcluded);
22
+ try {
23
+ await overlayTrackedEolDifferences(root, temporary);
24
+ const [modified, untracked] = await Promise.all([
25
+ gitBuffer(root, ["diff", "--name-only", "-z"]),
26
+ gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]),
27
+ ]);
28
+ for (const raw of new Set([
29
+ ...splitGitZero(modified),
30
+ ...splitGitZero(untracked),
31
+ ])) {
32
+ const relative = raw.replace(/\\/gu, "/");
33
+ if (workspacePathExcluded(relative, snapshotExcluded))
34
+ continue;
35
+ const source = path.join(root, ...relative.split("/"));
36
+ const target = path.join(temporary, ...relative.split("/"));
37
+ const info = await stat(source).catch(() => null);
38
+ if (!info?.isFile()) {
39
+ await rm(target, { recursive: true, force: true });
40
+ continue;
41
+ }
42
+ await mkdir(path.dirname(target), { recursive: true });
43
+ await copyFile(source, target);
44
+ }
45
+ await removeExcludedSnapshotPaths(temporary, snapshotExcluded);
46
+ await linkDependencyTrees(root, temporary, [
47
+ workdir,
48
+ ...additionalExcludedWorkdirs,
49
+ ]);
50
+ const after = await captureWorkspaceFingerprint(root, fingerprintExcluded);
51
+ if (after.identity !== before.identity)
52
+ throw new Error("workspace_changed_during_snapshot");
53
+ return {
54
+ root: temporary,
55
+ manifest,
56
+ preparation_ms: performance.now() - started,
57
+ dispose: () => rm(temporary, { recursive: true, force: true }),
58
+ };
59
+ }
60
+ catch (error) {
61
+ await rm(temporary, { recursive: true, force: true });
62
+ throw error;
63
+ }
64
+ }
65
+ async function checkoutIndexIntoFreshRoot(root, label, expectedFingerprint, fingerprintExcluded) {
66
+ for (let attempt = 1; attempt <= MAX_CHECKOUT_INDEX_ATTEMPTS; attempt += 1) {
67
+ const temporary = await mkdtemp(path.join(os.tmpdir(), `ty-context-${safe(label)}-`));
68
+ try {
69
+ await gitVoid(root, [
70
+ "checkout-index",
71
+ "--all",
72
+ "--force",
73
+ "--ignore-skip-worktree-bits",
74
+ `--prefix=${temporary.replace(/\\/gu, "/")}/`,
75
+ ]);
76
+ return temporary;
77
+ }
78
+ catch (error) {
79
+ await rm(temporary, { recursive: true, force: true });
80
+ if (!retryableUnclassifiedCheckout(error) ||
81
+ attempt === MAX_CHECKOUT_INDEX_ATTEMPTS)
82
+ throw error;
83
+ const current = await captureWorkspaceFingerprint(root, fingerprintExcluded);
84
+ if (current.identity !== expectedFingerprint)
85
+ throw new Error("workspace_changed_during_snapshot");
86
+ }
87
+ }
88
+ throw new Error("workspace_snapshot_checkout_attempts_exhausted");
89
+ }
90
+ function retryableUnclassifiedCheckout(error) {
91
+ return (error instanceof GitCommandError &&
92
+ error.exitCode === 1 &&
93
+ error.signal === null &&
94
+ error.stdoutBytes === 0 &&
95
+ error.stderrBytes === 0);
96
+ }
97
+ async function removeExcludedSnapshotPaths(snapshotRoot, excluded) {
98
+ for (const relative of excluded)
99
+ await rm(path.join(snapshotRoot, ...relative.split("/")), {
100
+ recursive: true,
101
+ force: true,
102
+ });
103
+ async function visit(directory) {
104
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
105
+ const target = path.join(directory, entry.name);
106
+ if (entry.isDirectory() && entry.name === "node_modules") {
107
+ await rm(target, { recursive: true, force: true });
108
+ }
109
+ else if (entry.isDirectory())
110
+ await visit(target);
111
+ }
112
+ }
113
+ await visit(snapshotRoot);
114
+ }
115
+ async function overlayTrackedEolDifferences(sourceRoot, snapshotRoot) {
116
+ const [raw, autocrlf] = await Promise.all([
117
+ gitBuffer(sourceRoot, ["ls-files", "--eol", "-z"]),
118
+ gitEffectiveConfigGet(sourceRoot, "core.autocrlf"),
119
+ ]);
120
+ for (const record of splitGitZero(raw)) {
121
+ const tab = record.indexOf("\t");
122
+ if (tab < 0)
123
+ continue;
124
+ const metadata = record.slice(0, tab);
125
+ const relative = record.slice(tab + 1).replace(/\\/gu, "/");
126
+ const match = metadata.match(/^i\/(\S+)\s+w\/(\S+)\s+attr\/(.*)$/u);
127
+ if (!match)
128
+ continue;
129
+ const [, indexEol, worktreeEol, attributesRaw] = match;
130
+ const expected = checkoutEol(indexEol, attributesRaw.trim(), autocrlf);
131
+ if (worktreeEol === expected)
132
+ continue;
133
+ const source = path.join(sourceRoot, ...relative.split("/"));
134
+ const target = path.join(snapshotRoot, ...relative.split("/"));
135
+ const info = await stat(source).catch(() => null);
136
+ if (!info?.isFile())
137
+ continue;
138
+ await mkdir(path.dirname(target), { recursive: true });
139
+ await copyFile(source, target);
140
+ }
141
+ }
142
+ function checkoutEol(indexEol, attributes, autocrlf) {
143
+ const explicit = attributes.match(/(?:^|\s)eol=(lf|crlf)(?:\s|$)/u)?.[1];
144
+ if (explicit)
145
+ return explicit;
146
+ if (/(?:^|\s)-text(?:\s|$)/u.test(attributes))
147
+ return indexEol;
148
+ if (indexEol === "-text" || indexEol === "none")
149
+ return indexEol;
150
+ return autocrlf?.toLowerCase() === "true" ? "crlf" : indexEol;
151
+ }
152
+ async function linkDependencyTrees(sourceRoot, snapshotRoot, workdirs) {
153
+ const protectedWorkdirs = workdirs.map((workdir) => repoRelative(sourceRoot, workdir));
154
+ async function visit(directory, relative = "") {
155
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
156
+ const next = relative ? `${relative}/${entry.name}` : entry.name;
157
+ if (protectedWorkdirs.some((protectedWorkdir) => next === protectedWorkdir ||
158
+ next.startsWith(`${protectedWorkdir}/`)) ||
159
+ entry.name === ".git")
160
+ continue;
161
+ const source = path.join(directory, entry.name);
162
+ if (entry.isDirectory() && entry.name === "node_modules") {
163
+ const target = path.join(snapshotRoot, ...next.split("/"));
164
+ await mkdir(path.dirname(target), { recursive: true });
165
+ await rm(target, { recursive: true, force: true });
166
+ await symlink(source, target, process.platform === "win32" ? "junction" : "dir");
167
+ }
168
+ else if (entry.isDirectory())
169
+ await visit(source, next);
170
+ }
171
+ }
172
+ await visit(sourceRoot);
173
+ }
174
+ function safe(value) {
175
+ return value.replace(/[^A-Za-z0-9._-]/gu, "-").slice(0, 80);
176
+ }
@@ -1,26 +1,5 @@
1
- import type { WorkspaceFingerprintV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
2
- export interface WorkspaceSnapshotV2 {
3
- root: string;
4
- manifest: WorkspaceManifestV2;
5
- preparation_ms: number;
6
- dispose(): Promise<void>;
7
- }
8
- export declare function repositoryRoot(start: string): Promise<string>;
9
- export declare function gitCommonDir(root: string): Promise<string>;
10
- export declare function captureWorkspaceFingerprint(rootInput: string, excludedPrefixes?: string[]): Promise<WorkspaceFingerprintV2>;
11
- export declare function captureWorkspaceManifest(rootInput: string, workdirInput: string, _copyRoot?: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceManifestV2>;
12
- export declare function createWorkspaceSnapshot(rootInput: string, workdirInput: string, label: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceSnapshotV2>;
13
- export declare function changedWorkspacePaths(baseline: WorkspaceManifestV2, current: WorkspaceManifestV2): string[];
14
- export declare function changedWorkspacePathsFromHead(rootInput: string, workdirInput: string, additionalExcludedWorkdirs?: string[]): Promise<string[]>;
15
- export declare function currentGitState(root: string): Promise<{
16
- head: string;
17
- tree: string;
18
- dirty: string[];
19
- }>;
20
- export declare function currentGitTree(root: string): Promise<string>;
21
- export declare function gitPath(root: string, pathSpec: string): Promise<string>;
22
- export declare function gitConfigGet(root: string, name: string): Promise<string | null>;
23
- export declare function gitConfigSet(root: string, name: string, value: string): Promise<void>;
24
- export declare function gitConfigUnset(root: string, name: string): Promise<void>;
25
- export declare function resolveInsideRepository(rootInput: string, relativeInput: string, label: string): string;
26
- export declare function repoRelative(rootInput: string, fileInput: string): string;
1
+ export { resolveInsideRepository } from "./repository-path-safety.js";
2
+ export { currentGitState, currentGitTree, gitCommonDir, gitConfigGet, gitConfigSet, gitConfigUnset, gitPath, repoRelative, repositoryRoot, } from "./long-task-git.js";
3
+ export { captureWorkspaceFingerprint, captureWorkspaceManifest, changedWorkspacePaths, changedWorkspacePathsFromHead, } from "./long-task-workspace-manifest.js";
4
+ export { createWorkspaceSnapshot } from "./long-task-workspace-snapshot.js";
5
+ export type { WorkspaceSnapshotV2 } from "./long-task-workspace-snapshot.js";