project-tiny-context-harness 0.8.12 → 0.8.13

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 (158) hide show
  1. package/README.md +43 -19
  2. package/assets/README.md +81 -40
  3. package/assets/README.zh-CN.md +44 -22
  4. package/assets/agents/AGENTS_CORE.md +8 -4
  5. package/assets/skills/context_development_engineer/SKILL.md +9 -1
  6. package/assets/skills/design-resource-authoring/SKILL.md +7 -5
  7. package/assets/skills/design-resource-authoring/references/downstream-handoff.md +2 -2
  8. package/assets/skills/design-resource-authoring/references/open-design-provider.md +22 -2
  9. package/assets/skills/design-resource-authoring/references/recovery-and-writeback.md +199 -0
  10. package/assets/skills/design-resource-authoring/references/resource-selection.md +2 -2
  11. package/assets/skills/long-task-workflow/SKILL.md +6 -6
  12. package/assets/skills/long-task-workflow/references/contract-authoring.md +27 -22
  13. package/assets/skills/long-task-workflow/references/evidence-design.md +49 -32
  14. package/assets/skills/long-task-workflow/references/source-authoring.md +2 -2
  15. package/dist/commands/design-resource-recovery.d.ts +1 -0
  16. package/dist/commands/design-resource-recovery.js +152 -0
  17. package/dist/commands/design-resource.js +20 -1
  18. package/dist/commands/index.js +1 -1
  19. package/dist/lib/design-resource-reconciliation-codec.d.ts +2 -0
  20. package/dist/lib/design-resource-reconciliation-codec.js +174 -0
  21. package/dist/lib/design-resource-reconciliation-types.d.ts +62 -0
  22. package/dist/lib/design-resource-reconciliation-types.js +1 -0
  23. package/dist/lib/design-resource-reconciliation.d.ts +3 -0
  24. package/dist/lib/design-resource-reconciliation.js +224 -0
  25. package/dist/lib/design-resource-recovery-authority-policy.d.ts +8 -0
  26. package/dist/lib/design-resource-recovery-authority-policy.js +123 -0
  27. package/dist/lib/design-resource-recovery-catalog-resources.d.ts +6 -0
  28. package/dist/lib/design-resource-recovery-catalog-resources.js +121 -0
  29. package/dist/lib/design-resource-recovery-catalog-shape.d.ts +3 -0
  30. package/dist/lib/design-resource-recovery-catalog-shape.js +104 -0
  31. package/dist/lib/design-resource-recovery-catalog.d.ts +9 -0
  32. package/dist/lib/design-resource-recovery-catalog.js +103 -0
  33. package/dist/lib/design-resource-recovery-cleanup.d.ts +2 -0
  34. package/dist/lib/design-resource-recovery-cleanup.js +11 -0
  35. package/dist/lib/design-resource-recovery-codec-primitives.d.ts +19 -0
  36. package/dist/lib/design-resource-recovery-codec-primitives.js +92 -0
  37. package/dist/lib/design-resource-recovery-codec.d.ts +4 -0
  38. package/dist/lib/design-resource-recovery-codec.js +93 -0
  39. package/dist/lib/design-resource-recovery-current.d.ts +9 -0
  40. package/dist/lib/design-resource-recovery-current.js +41 -0
  41. package/dist/lib/design-resource-recovery-delta-policy.d.ts +5 -0
  42. package/dist/lib/design-resource-recovery-delta-policy.js +108 -0
  43. package/dist/lib/design-resource-recovery-files.d.ts +37 -0
  44. package/dist/lib/design-resource-recovery-files.js +204 -0
  45. package/dist/lib/design-resource-recovery-final-disposition-shape.d.ts +2 -0
  46. package/dist/lib/design-resource-recovery-final-disposition-shape.js +47 -0
  47. package/dist/lib/design-resource-recovery-patch-types.d.ts +54 -0
  48. package/dist/lib/design-resource-recovery-patch-types.js +1 -0
  49. package/dist/lib/design-resource-recovery-replay.d.ts +7 -0
  50. package/dist/lib/design-resource-recovery-replay.js +112 -0
  51. package/dist/lib/design-resource-recovery-repository-bindings.d.ts +9 -0
  52. package/dist/lib/design-resource-recovery-repository-bindings.js +49 -0
  53. package/dist/lib/design-resource-recovery-schema.d.ts +5 -0
  54. package/dist/lib/design-resource-recovery-schema.js +5 -0
  55. package/dist/lib/design-resource-recovery-shape.d.ts +10 -0
  56. package/dist/lib/design-resource-recovery-shape.js +184 -0
  57. package/dist/lib/design-resource-recovery-source-authority.d.ts +5 -0
  58. package/dist/lib/design-resource-recovery-source-authority.js +219 -0
  59. package/dist/lib/design-resource-recovery-text.d.ts +17 -0
  60. package/dist/lib/design-resource-recovery-text.js +186 -0
  61. package/dist/lib/design-resource-recovery-types.d.ts +197 -0
  62. package/dist/lib/design-resource-recovery-types.js +1 -0
  63. package/dist/lib/design-resource-recovery-writeback-policy.d.ts +5 -0
  64. package/dist/lib/design-resource-recovery-writeback-policy.js +326 -0
  65. package/dist/lib/design-resource-recovery-writeback-shape.d.ts +6 -0
  66. package/dist/lib/design-resource-recovery-writeback-shape.js +155 -0
  67. package/dist/lib/design-resource-recovery.d.ts +44 -0
  68. package/dist/lib/design-resource-recovery.js +216 -0
  69. package/dist/lib/long-task-acceptance-shape.js +8 -2
  70. package/dist/lib/long-task-activation-validation.js +13 -3
  71. package/dist/lib/long-task-admitted-observation-records.d.ts +25 -0
  72. package/dist/lib/long-task-admitted-observation-records.js +40 -0
  73. package/dist/lib/long-task-admitted-observation.d.ts +93 -0
  74. package/dist/lib/long-task-admitted-observation.js +201 -0
  75. package/dist/lib/long-task-artifacts.d.ts +1 -0
  76. package/dist/lib/long-task-artifacts.js +1 -0
  77. package/dist/lib/long-task-authority-material-diff.js +1 -0
  78. package/dist/lib/long-task-authority-policy.d.ts +2 -0
  79. package/dist/lib/long-task-authority-policy.js +2 -0
  80. package/dist/lib/long-task-authority-revision-details.js +3 -1
  81. package/dist/lib/long-task-authority.js +2 -1
  82. package/dist/lib/long-task-check-execution-policy.js +5 -0
  83. package/dist/lib/long-task-check-runner.d.ts +2 -2
  84. package/dist/lib/long-task-check-runner.js +440 -23
  85. package/dist/lib/long-task-claims.d.ts +18 -0
  86. package/dist/lib/long-task-claims.js +35 -0
  87. package/dist/lib/long-task-compact-structure-targets.js +1 -0
  88. package/dist/lib/long-task-conformance-policy.js +3 -1
  89. package/dist/lib/long-task-counterfactual-claim-policy.d.ts +3 -4
  90. package/dist/lib/long-task-counterfactual-claim-policy.js +11 -3
  91. package/dist/lib/long-task-counterfactual-sandbox.js +1 -0
  92. package/dist/lib/long-task-counterfactual-types.d.ts +2 -0
  93. package/dist/lib/long-task-delivery-compiler.js +2 -0
  94. package/dist/lib/long-task-evidence-capability-policy.d.ts +3 -2
  95. package/dist/lib/long-task-evidence-capability-policy.js +114 -8
  96. package/dist/lib/long-task-evidence-capability-runtime.d.ts +5 -2
  97. package/dist/lib/long-task-evidence-capability-runtime.js +149 -5
  98. package/dist/lib/long-task-evidence-sensitivity-policy.d.ts +9 -0
  99. package/dist/lib/long-task-evidence-sensitivity-policy.js +31 -1
  100. package/dist/lib/long-task-evidence-v2.d.ts +4 -4
  101. package/dist/lib/long-task-evidence-v2.js +321 -43
  102. package/dist/lib/long-task-exact-comparison.d.ts +16 -0
  103. package/dist/lib/long-task-exact-comparison.js +28 -0
  104. package/dist/lib/long-task-execution-observation.d.ts +24 -0
  105. package/dist/lib/long-task-execution-observation.js +332 -0
  106. package/dist/lib/long-task-explain-acceptance-link.d.ts +4 -0
  107. package/dist/lib/long-task-explain-acceptance-link.js +4 -0
  108. package/dist/lib/long-task-explain-claim-links.d.ts +2 -0
  109. package/dist/lib/long-task-explain-claim-links.js +2 -0
  110. package/dist/lib/long-task-explain-source-links.d.ts +6 -0
  111. package/dist/lib/long-task-final-v2.js +7 -4
  112. package/dist/lib/long-task-json-pointer-observation.d.ts +59 -0
  113. package/dist/lib/long-task-json-pointer-observation.js +184 -0
  114. package/dist/lib/long-task-observation-artifact.d.ts +8 -0
  115. package/dist/lib/long-task-observation-artifact.js +58 -0
  116. package/dist/lib/long-task-observation-authority.d.ts +17 -0
  117. package/dist/lib/long-task-observation-authority.js +348 -0
  118. package/dist/lib/long-task-paths.js +1 -1
  119. package/dist/lib/long-task-process-observation.d.ts +6 -0
  120. package/dist/lib/long-task-process-observation.js +117 -0
  121. package/dist/lib/long-task-process-runtime-closure.d.ts +13 -0
  122. package/dist/lib/long-task-process-runtime-closure.js +175 -0
  123. package/dist/lib/long-task-progress.js +2 -0
  124. package/dist/lib/long-task-protected-files.d.ts +1 -1
  125. package/dist/lib/long-task-protected-files.js +1 -30
  126. package/dist/lib/long-task-risk.js +8 -3
  127. package/dist/lib/long-task-root-shape.js +10 -1
  128. package/dist/lib/long-task-runner-environment.d.ts +1 -1
  129. package/dist/lib/long-task-runner-environment.js +4 -4
  130. package/dist/lib/long-task-runner-freeze.d.ts +4 -2
  131. package/dist/lib/long-task-runner-freeze.js +128 -28
  132. package/dist/lib/long-task-runtime-types.d.ts +111 -0
  133. package/dist/lib/long-task-scoped-binding.d.ts +13 -0
  134. package/dist/lib/long-task-scoped-binding.js +11 -0
  135. package/dist/lib/long-task-semantic-contract-types.d.ts +1 -0
  136. package/dist/lib/long-task-semantic-fact-contract-closure.js +3 -3
  137. package/dist/lib/long-task-semantic-fact-evidence.d.ts +3 -3
  138. package/dist/lib/long-task-semantic-fact-evidence.js +75 -58
  139. package/dist/lib/long-task-source-claim-validation.js +4 -1
  140. package/dist/lib/long-task-source-target-continuity.d.ts +2 -2
  141. package/dist/lib/long-task-source-target-continuity.js +35 -2
  142. package/dist/lib/long-task-source-target-index.d.ts +3 -0
  143. package/dist/lib/long-task-source-target-index.js +19 -0
  144. package/dist/lib/long-task-stage-policy.js +10 -4
  145. package/dist/lib/long-task-static-observation-freeze.d.ts +83 -0
  146. package/dist/lib/long-task-static-observation-freeze.js +428 -0
  147. package/dist/lib/long-task-status-projection.js +5 -1
  148. package/dist/lib/long-task-target-policy.d.ts +1 -1
  149. package/dist/lib/long-task-target-policy.js +3 -0
  150. package/dist/lib/long-task-verifier-dependency-closure.js +7 -3
  151. package/dist/lib/long-task-verifier-v2.js +70 -9
  152. package/dist/lib/long-task-workspace.d.ts +1 -1
  153. package/dist/lib/long-task-workspace.js +1 -12
  154. package/dist/lib/repository-path-safety.d.ts +14 -0
  155. package/dist/lib/repository-path-safety.js +126 -0
  156. package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +53 -8
  157. package/migrations/README.md +77 -0
  158. 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}`);
@@ -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) {
@@ -1,3 +1,4 @@
1
+ export { resolveInsideRepository } from "./repository-path-safety.js";
1
2
  import type { WorkspaceFingerprintV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
2
3
  export interface WorkspaceSnapshotV2 {
3
4
  root: string;
@@ -22,5 +23,4 @@ export declare function gitPath(root: string, pathSpec: string): Promise<string>
22
23
  export declare function gitConfigGet(root: string, name: string): Promise<string | null>;
23
24
  export declare function gitConfigSet(root: string, name: string, value: string): Promise<void>;
24
25
  export declare function gitConfigUnset(root: string, name: string): Promise<void>;
25
- export declare function resolveInsideRepository(rootInput: string, relativeInput: string, label: string): string;
26
26
  export declare function repoRelative(rootInput: string, fileInput: string): string;
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, symlink, } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ export { resolveInsideRepository } from "./repository-path-safety.js";
5
6
  import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
6
7
  export async function repositoryRoot(start) {
7
8
  const resolved = path.resolve(start);
@@ -247,18 +248,6 @@ export async function gitConfigUnset(root, name) {
247
248
  throw error;
248
249
  }
249
250
  }
250
- export function resolveInsideRepository(rootInput, relativeInput, label) {
251
- if (!relativeInput || path.isAbsolute(relativeInput))
252
- throw new Error(`unsafe_path:${label}:${relativeInput}`);
253
- const normalized = relativeInput.replace(/\\/gu, "/");
254
- if (normalized.split("/").includes(".."))
255
- throw new Error(`unsafe_path:${label}:${relativeInput}`);
256
- const root = path.resolve(rootInput);
257
- const resolved = path.resolve(root, ...normalized.split("/"));
258
- if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`))
259
- throw new Error(`unsafe_path:${label}:${relativeInput}`);
260
- return resolved;
261
- }
262
251
  export function repoRelative(rootInput, fileInput) {
263
252
  const value = path
264
253
  .relative(path.resolve(rootInput), path.resolve(fileInput))
@@ -0,0 +1,14 @@
1
+ import type { Stats } from "node:fs";
2
+ export interface SafeRepositoryPath {
3
+ repository: string;
4
+ absolute: string;
5
+ relative: string;
6
+ }
7
+ export declare function resolveInsideRepository(rootInput: string, relativeInput: string, label: string): string;
8
+ export declare function assertProtectedRepositoryFile(repositoryInput: string, fileInput: string, label: string): Promise<string>;
9
+ export declare function assertSafeRepositoryFilePath(repositoryInput: string, relativeInput: string, label: string, options?: {
10
+ destinationMayBeAbsent?: boolean;
11
+ }): Promise<SafeRepositoryPath & {
12
+ status: Stats | null;
13
+ }>;
14
+ export declare function ensureSafeRepositoryDirectory(repositoryInput: string, relativeInput: string, label: string): Promise<SafeRepositoryPath>;
@@ -0,0 +1,126 @@
1
+ import { lstat, mkdir, realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export function resolveInsideRepository(rootInput, relativeInput, label) {
4
+ if (!relativeInput || path.isAbsolute(relativeInput))
5
+ throw new Error(`unsafe_path:${label}:${relativeInput}`);
6
+ const normalized = relativeInput.replace(/\\/gu, "/");
7
+ if (normalized.split("/").some((segment) => segment === "..") ||
8
+ normalized.startsWith("//") ||
9
+ /^[A-Za-z]:/u.test(normalized))
10
+ throw new Error(`unsafe_path:${label}:${relativeInput}`);
11
+ const root = path.resolve(rootInput);
12
+ const resolved = path.resolve(root, ...normalized.split("/"));
13
+ if (!inside(root, resolved))
14
+ throw new Error(`unsafe_path:${label}:${relativeInput}`);
15
+ return resolved;
16
+ }
17
+ export async function assertProtectedRepositoryFile(repositoryInput, fileInput, label) {
18
+ const repositoryPath = path.resolve(repositoryInput);
19
+ const repository = await realpath(repositoryPath);
20
+ const candidate = path.resolve(fileInput);
21
+ assertInside(repositoryPath, candidate, label, fileInput);
22
+ await assertExistingParents(repositoryPath, repository, candidate, label);
23
+ const info = await lstatOrNull(candidate);
24
+ if (!info)
25
+ throw new Error(`protected_input_not_found:${label}:${display(fileInput)}`);
26
+ assertRegularNoFollow(info, label, fileInput);
27
+ const resolved = await realpath(candidate);
28
+ assertInside(repository, resolved, label, fileInput);
29
+ return resolved;
30
+ }
31
+ export async function assertSafeRepositoryFilePath(repositoryInput, relativeInput, label, options = {}) {
32
+ const repositoryPath = path.resolve(repositoryInput);
33
+ const repository = await realpath(repositoryPath);
34
+ const absolute = resolveInsideRepository(repositoryPath, relativeInput, label);
35
+ await assertExistingParents(repositoryPath, repository, absolute, label);
36
+ const status = await lstatOrNull(absolute);
37
+ if (!status && !options.destinationMayBeAbsent)
38
+ throw new Error(`protected_input_not_found:${label}:${display(relativeInput)}`);
39
+ if (status) {
40
+ assertRegularNoFollow(status, label, relativeInput);
41
+ assertInside(repository, await realpath(absolute), label, relativeInput);
42
+ }
43
+ return {
44
+ repository,
45
+ absolute,
46
+ relative: repositoryRelative(repositoryPath, absolute, label),
47
+ status,
48
+ };
49
+ }
50
+ export async function ensureSafeRepositoryDirectory(repositoryInput, relativeInput, label) {
51
+ const repositoryPath = path.resolve(repositoryInput);
52
+ const repository = await realpath(repositoryPath);
53
+ const absolute = resolveInsideRepository(repositoryPath, relativeInput, label);
54
+ const relative = repositoryRelative(repositoryPath, absolute, label);
55
+ let cursor = repositoryPath;
56
+ for (const segment of relative.split(path.sep)) {
57
+ cursor = path.join(cursor, segment);
58
+ let status = await lstatOrNull(cursor);
59
+ if (!status) {
60
+ await mkdir(cursor).catch((error) => {
61
+ if (error.code !== "EEXIST")
62
+ throw error;
63
+ });
64
+ status = await lstatOrNull(cursor);
65
+ }
66
+ if (!status || status.isSymbolicLink() || !status.isDirectory())
67
+ throw new Error(`unsafe_repository_directory:${label}:${display(relativeInput)}`);
68
+ assertInside(repository, await realpath(cursor), label, relativeInput);
69
+ }
70
+ return { repository, absolute, relative: relative.replace(/\\/gu, "/") };
71
+ }
72
+ async function assertExistingParents(lexicalRepository, canonicalRepository, candidate, label) {
73
+ const relative = repositoryRelative(lexicalRepository, path.dirname(candidate), label);
74
+ if (!relative)
75
+ return;
76
+ let cursor = lexicalRepository;
77
+ for (const segment of relative.split(path.sep)) {
78
+ cursor = path.join(cursor, segment);
79
+ const status = await lstatOrNull(cursor);
80
+ if (!status)
81
+ throw new Error(`protected_input_parent_not_found:${label}:${display(cursor)}`);
82
+ if (status.isSymbolicLink())
83
+ throw new Error(`protected_input_parent_symlink_not_allowed:${label}:${display(cursor)}`);
84
+ if (!status.isDirectory())
85
+ throw new Error(`protected_input_parent_not_directory:${label}:${display(cursor)}`);
86
+ assertInside(canonicalRepository, await realpath(cursor), label, cursor);
87
+ }
88
+ }
89
+ function assertRegularNoFollow(info, label, original) {
90
+ if (info.isSymbolicLink())
91
+ throw new Error(`protected_input_symlink_not_allowed:${label}:${display(original)}`);
92
+ if (!info.isFile())
93
+ throw new Error(`protected_input_not_regular_file:${label}:${display(original)}`);
94
+ if (typeof info.nlink === "number" && info.nlink > 1)
95
+ throw new Error(`protected_input_hardlink_not_allowed:${label}:${display(original)}`);
96
+ }
97
+ function repositoryRelative(repository, candidate, label) {
98
+ const relative = path.relative(repository, candidate);
99
+ if (!inside(repository, candidate))
100
+ throw new Error(`protected_input_realpath_outside_repository:${label}:${display(candidate)}`);
101
+ return relative;
102
+ }
103
+ function assertInside(repository, candidate, label, original) {
104
+ if (!inside(repository, candidate))
105
+ throw new Error(`protected_input_realpath_outside_repository:${label}:${display(original)}`);
106
+ }
107
+ function inside(repository, candidate) {
108
+ const relative = path.relative(repository, candidate);
109
+ return (relative === "" ||
110
+ (relative !== ".." &&
111
+ !relative.startsWith(`..${path.sep}`) &&
112
+ !path.isAbsolute(relative)));
113
+ }
114
+ async function lstatOrNull(target) {
115
+ try {
116
+ return await lstat(target);
117
+ }
118
+ catch (error) {
119
+ if (error.code === "ENOENT")
120
+ return null;
121
+ throw error;
122
+ }
123
+ }
124
+ function display(file) {
125
+ return file.replace(/\\/gu, "/");
126
+ }
@@ -169,15 +169,39 @@
169
169
  ],
170
170
  "properties": {
171
171
  "claims": { "type": "integer", "minimum": 0, "maximum": 1000000 },
172
- "claim_projections": { "type": "integer", "minimum": 0, "maximum": 2000000 },
173
- "selector_members": { "type": "integer", "minimum": 0, "maximum": 5000000 },
174
- "structure_families": { "type": "integer", "minimum": 0, "maximum": 1000000 },
175
- "structure_references": { "type": "integer", "minimum": 0, "maximum": 10000000 },
176
- "structure_arguments": { "type": "integer", "minimum": 0, "maximum": 50000000 },
172
+ "claim_projections": {
173
+ "type": "integer",
174
+ "minimum": 0,
175
+ "maximum": 2000000
176
+ },
177
+ "selector_members": {
178
+ "type": "integer",
179
+ "minimum": 0,
180
+ "maximum": 5000000
181
+ },
182
+ "structure_families": {
183
+ "type": "integer",
184
+ "minimum": 0,
185
+ "maximum": 1000000
186
+ },
187
+ "structure_references": {
188
+ "type": "integer",
189
+ "minimum": 0,
190
+ "maximum": 10000000
191
+ },
192
+ "structure_arguments": {
193
+ "type": "integer",
194
+ "minimum": 0,
195
+ "maximum": 50000000
196
+ },
177
197
  "facts": { "type": "integer", "minimum": 0, "maximum": 1000000 },
178
198
  "obligations": { "type": "integer", "minimum": 0, "maximum": 2000000 },
179
199
  "assertions": { "type": "integer", "minimum": 0, "maximum": 5000000 },
180
- "canonical_bytes": { "type": "integer", "minimum": 0, "maximum": 268435456 }
200
+ "canonical_bytes": {
201
+ "type": "integer",
202
+ "minimum": 0,
203
+ "maximum": 268435456
204
+ }
181
205
  }
182
206
  },
183
207
  "longTaskCompactCarrier": {
@@ -229,9 +253,18 @@
229
253
  "items": {
230
254
  "type": "object",
231
255
  "additionalProperties": false,
232
- "required": ["key", "digest", "boundary", "parameter_count", "template"],
256
+ "required": [
257
+ "key",
258
+ "digest",
259
+ "boundary",
260
+ "parameter_count",
261
+ "template"
262
+ ],
233
263
  "properties": {
234
- "key": { "type": "string", "pattern": "^structure\\.[a-f0-9]{24}$" },
264
+ "key": {
265
+ "type": "string",
266
+ "pattern": "^structure\\.[a-f0-9]{24}$"
267
+ },
235
268
  "digest": { "$ref": "#/$defs/sha256" },
236
269
  "boundary": { "$ref": "#/$defs/semanticRef" },
237
270
  "parameter_count": { "type": "integer", "minimum": 0 },
@@ -464,6 +497,10 @@
464
497
  ]
465
498
  },
466
499
  "root_entrypoint": { "$ref": "#/$defs/nonEmpty" },
500
+ "root_argv": {
501
+ "type": "array",
502
+ "items": { "type": "string", "pattern": "^[^\\u0000]*$" }
503
+ },
467
504
  "capabilities": {
468
505
  "type": "array",
469
506
  "minItems": 1,
@@ -2011,6 +2048,10 @@
2011
2048
  "check_key": { "$ref": "#/$defs/key" },
2012
2049
  "expected_assertion_failures": { "$ref": "#/$defs/keys" },
2013
2050
  "preserved_assertions": { "$ref": "#/$defs/keys", "default": [] },
2051
+ "allowed_fanout_assertions": {
2052
+ "$ref": "#/$defs/keys",
2053
+ "default": []
2054
+ },
2014
2055
  "mutation": {
2015
2056
  "oneOf": [
2016
2057
  {
@@ -2091,6 +2132,10 @@
2091
2132
  "uniqueItems": true
2092
2133
  },
2093
2134
  "preserved_assertions": { "$ref": "#/$defs/keys", "default": [] },
2135
+ "allowed_fanout_assertions": {
2136
+ "$ref": "#/$defs/keys",
2137
+ "default": []
2138
+ },
2094
2139
  "mutation": {
2095
2140
  "oneOf": [
2096
2141
  {
@@ -13,3 +13,80 @@ V2 file predates explicit Stage, target-profile/root-runtime, journey-scenario,
13
13
  success/degradation, evidence-capability or external-impact authority. Those
14
14
  meanings must be re-authored from Source; migration never infers them from old
15
15
  Progress or Receipts.
16
+
17
+ ## Admitted-observation/runtime-TCB revision
18
+
19
+ The next Long-Task compatibility boundary intentionally does not provide a
20
+ mechanical Contract rewrite. Machine evidence that depends on a custom Oracle,
21
+ a project-submitted runtime/interaction/state/verdict row, a runner-created or
22
+ runner-modified carrier, an indirect wrapper, an unsupported target family or
23
+ a Counterfactual with no package-admitted actual must be re-authored explicitly.
24
+ Migration must never silently:
25
+
26
+ - replace a custom Oracle with a package Oracle;
27
+ - turn machine proof into `external_confirmation`;
28
+ - replace a verifier wrapper with a product root entrypoint;
29
+ - infer a static structure carrier from `Binding`/`input_paths` alone; or
30
+ - infer a process runtime dependency from `input_paths`, runner or verification membership;
31
+ - invent a Source-backed execution target or production Binding for an old root; or
32
+ - preserve a known false-acceptance path as compatibility behavior.
33
+
34
+ Greenfield authoring may declare an exact process root, argv repository file
35
+ or production carrier as `planned`. Its exact Source/Binding declaration is
36
+ stable through Preflight/Compile and later materialization, but Final Gate
37
+ requires every compiled closure member to exist. A glob, a manifest sibling or
38
+ an undeclared repository-file argv token cannot substitute; migration does not
39
+ invent these exact declarations or parse language dependencies.
40
+
41
+ Preflight and doctor should report the narrow failing obligation and an
42
+ actionable manual route through diagnostics such as
43
+ `machine_observer_not_admitted`,
44
+ `semantic_fact_machine_observer_not_admitted`,
45
+ `unsupported_observer_requires_external_confirmation`,
46
+ `custom_oracle_machine_completion_forbidden`,
47
+ `static_observation_not_in_pre_run_snapshot`,
48
+ `static_observation_changed_by_runner`,
49
+ `process_observer_direct_root_required`,
50
+ `process_observer_root_invocation_required`,
51
+ `process_observer_root_argv_mismatch`,
52
+ `process_observation_input_changed_by_runner`,
53
+ `process_runtime_input_expected_authority_forbidden`,
54
+ `process_runtime_input_verification_role_forbidden`,
55
+ `process_runtime_input_evidence_role_forbidden`,
56
+ `process_root_source_binding_required`,
57
+ `process_root_production_binding_required`,
58
+ `process_root_source_identity_mismatch`,
59
+ `process_runtime_carrier_exact_path_required`,
60
+ `process_runtime_input_missing`,
61
+ `process_runtime_closure_identity_mismatch`,
62
+ `legacy_target_runtime_non_authoritative`,
63
+ `counterfactual_admitted_observation_required`,
64
+ `counterfactual_runtime_reachability_unproven` and
65
+ `project_submitted_verdict_disagrees_with_harness`. The user or Agent must then
66
+ choose an admitted static/direct-process observation or the existing blocking
67
+ External Confirmation. That semantic choice is `manual_required`, not ordinary
68
+ `sync` or a migration-registry inference; release guidance must therefore use
69
+ the `manual-required` update mode for affected consumers.
70
+
71
+ When the whole affected Outcome is unsupported, re-author it explicitly as an
72
+ external-only route: set `success_path_required: false`, remove machine Checks,
73
+ bind every ordinary/global and Semantic Fact Claim through exact
74
+ `impact_claims`, keep every Semantic Fact proof's explicit `confirmation_ref`,
75
+ and ensure a `blocks_target: true` confirmation impacts each
76
+ Stage Gate result Claim. A non-blocking confirmation or one missing gate-result
77
+ lineage cannot replace a Stage Gate, and migration never invents these choices.
78
+ The valid route ends as `blocked_external`, not either machine-accepted status.
79
+
80
+ Changing verifier identity, Compile admission policy or the observation TCB
81
+ invalidates the prior Active Authority. The Contract must be recompiled or
82
+ adopted through an explicit Authority Revision; old Progress, evidence and
83
+ Receipt are not reusable on the new boundary. Public
84
+ `long-task-check-result-v3` remains the compatibility payload, but its
85
+ project-authored actual/pass/verdict/runtime fields are non-authoritative. The
86
+ Harness-owned process adapter captures the separate
87
+ `ty-context-product-observation-v1` envelope from bounded stdout inside an
88
+ isolated frozen runtime-closure snapshot. It exposes no output-path, challenge
89
+ or protocol environment variable to the child; any execution nonce remains
90
+ internal host-attestation data. No v4 is introduced by this revision; a future
91
+ v4 requires a distinct need to carry a live browser/native session handle
92
+ across processes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-tiny-context-harness",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
4
4
  "description": "Minimal project memory and validation harness for AI coding agents.",
5
5
  "license": "MIT",
6
6
  "author": "Seven128",