@tiinex/core 0.11.0 → 0.13.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 (36) hide show
  1. package/package.json +6 -5
  2. package/src/audit/audit.run.js +1 -1
  3. package/src/public/index.js +18 -0
  4. package/src/public/node.js +1 -0
  5. package/src/schemas/creation.contracts.js +1 -1
  6. package/src/schemas/creation.schemaReferences.js +28 -8
  7. package/src/schemas/schema.reference.js +30 -4
  8. package/src/schemas/tiinex.root.v1.schema.json +2 -1
  9. package/src/tooling/portable/adapters/cli/cli.command-input.js +5 -0
  10. package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
  11. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +5 -1
  12. package/src/tooling/portable/adapters/cli/cli.help.js +15 -2
  13. package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
  14. package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +6 -6
  15. package/src/tooling/portable/adapters/cli/cli.source-frontier-reconciliation.js +18 -0
  16. package/src/tooling/portable/adapters/node/handoff.manufacture.bootstrap.js +37 -1
  17. package/src/tooling/portable/adapters/node/handoff.manufacture.enumeration.js +27 -8
  18. package/src/tooling/portable/adapters/node/handoff.manufacture.js +27 -0
  19. package/src/tooling/portable/adapters/node/handoff.manufacture.runtimeSource.js +82 -0
  20. package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +2 -1
  21. package/src/tooling/portable/adapters/node/sourceFrontierReconciliationProof.js +23 -0
  22. package/src/tooling/portable/adapters/node/workspaceCarrier.manufacture.js +8 -1
  23. package/src/tooling/portable/audit/audit.capability.js +2 -1
  24. package/src/tooling/portable/comparison/sourceFrontierComparison.js +54 -4
  25. package/src/tooling/portable/comparison/sourceFrontierReconciliationProof.js +569 -0
  26. package/src/tooling/portable/draft/draft.create.js +3 -0
  27. package/src/tooling/portable/draft/draft.operations.js +1 -1
  28. package/src/tooling/portable/editor/editor.assistance.js +2 -25
  29. package/src/tooling/portable/handoff/manufacture.js +15 -3
  30. package/src/tooling/portable/handoff/pointerEntrypoint.js +4 -1
  31. package/src/tooling/portable/handoff/schemaReferencePreflight.js +26 -0
  32. package/src/tooling/portable/handoff/transportEnvelopeV1.js +5 -0
  33. package/src/tooling/portable/index.js +1 -0
  34. package/src/tooling/portable/operation.catalog.js +8 -0
  35. package/src/tooling/portable/source/sourceEligibility.js +108 -0
  36. package/src/validation/validateArtifact.js +62 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiinex/core",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Shared host-neutral Tiinex implementation core for artifacts, schemas, validation, lineage, grounding, Handoffs, provenance and deterministic workflows.",
5
5
  "type": "module",
6
6
  "sideEffects": true,
@@ -134,6 +134,7 @@
134
134
  "./schema-bootstrap": "./src/tooling/portable/schema/bootstrap/canonical.pack.js",
135
135
  "./node/release": "./src/release/run.mjs",
136
136
  "./node/release-policy": "./src/release/policy.mjs",
137
+ "./tooling/portable/source/sourceEligibility.js": "./src/tooling/portable/source/sourceEligibility.js",
137
138
  "./tooling/portable/comparison/sourceFrontierComparison.js": "./src/tooling/portable/comparison/sourceFrontierComparison.js",
138
139
  "./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js"
139
140
  },
@@ -167,12 +168,12 @@
167
168
  "type": "git",
168
169
  "url": "git+https://github.com/Tiinex/core.git"
169
170
  },
170
- "gitHead": "d9fce40b1ec63fb0e4dc0a673161650d1661a25c",
171
+ "gitHead": "7e705cbd611919cc46659f9bcee0cf363a09c41c",
171
172
  "tiinexRelease": {
172
173
  "policy": "tiinex.master-npm-release.v1",
173
- "sourceCommit": "d9fce40b1ec63fb0e4dc0a673161650d1661a25c",
174
- "sourceTree": "1827ddd3975215d019cb809f2d477de6ebc4b300",
174
+ "sourceCommit": "7e705cbd611919cc46659f9bcee0cf363a09c41c",
175
+ "sourceTree": "f6b7df8dfa2757bd05cd4d9f764cd0696cc23797",
175
176
  "repository": "Tiinex/core",
176
- "previousVersion": "0.10.0"
177
+ "previousVersion": "0.12.0"
177
178
  }
178
179
  }
@@ -13,7 +13,7 @@ export function runAudit(scope = {}) {
13
13
  if (isPlainSupportingMarkdown(parsed, record)) return supportingMarkdownAuditResult(record, parsed, availability);
14
14
 
15
15
  const schemaId = parsed.envelope.current.schema.id || record?.schemaId || record?.currentSchemaId || '';
16
- const validationResult = validateArtifact({ parsed, schemaId, record, markdown, schemaReferenceAuthorities: scope.schemaReferenceAuthorities || null, validationContractOverride: scope.validationContractOverride || null, schemaValidationAuthority: scope.schemaValidationAuthority || null });
16
+ const validationResult = validateArtifact({ parsed, schemaId, record, markdown, schemaReferenceAuthorities: scope.schemaReferenceAuthorities || null, schemaReferenceContext: scope.schemaReferenceContext || 'historical', validationContractOverride: scope.validationContractOverride || null, schemaValidationAuthority: scope.schemaValidationAuthority || null });
17
17
  const findings = validationResult.findings;
18
18
  return {
19
19
  status: findings.some((finding) => finding.severity === 'error') ? 'invalid-or-incomplete' : validationResult.resolution.fallbackUsed ? 'degraded' : 'readable',
@@ -24,6 +24,17 @@ export {
24
24
  secureTransportV1AuthenticatedMetadata
25
25
  } from '../transport/secureTransportV1.js';
26
26
 
27
+ export {
28
+ PORTABLE_SOURCE_ELIGIBILITY_SCHEMA_ID,
29
+ PORTABLE_GENERATED_SOURCE_EXCLUDED_DIRECTORY_NAMES,
30
+ PORTABLE_GENERATED_SOURCE_EXCLUDED_FILE_SUFFIXES,
31
+ DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES,
32
+ DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS,
33
+ qualifyPortableSourcePath,
34
+ isPortableSourceEligiblePath,
35
+ portableSourceEligibilityPolicy
36
+ } from '../tooling/portable/source/sourceEligibility.js';
37
+
27
38
  export {
28
39
  PORTABLE_SOURCE_FRONTIER_SCHEMA_ID,
29
40
  PORTABLE_SOURCE_FRONTIER_COMPARISON_SCHEMA_ID,
@@ -35,3 +46,10 @@ export {
35
46
  compareOrReconcilePortableSourceFrontiers,
36
47
  projectPortableSourceFrontierComparisonSummary
37
48
  } from '../tooling/portable/comparison/sourceFrontierComparison.js';
49
+ export {
50
+ PORTABLE_SOURCE_FRONTIER_RECONCILIATION_PROOF_SCHEMA_ID,
51
+ PORTABLE_SOURCE_FRONTIER_RECONCILIATION_SUMMARY_SCHEMA_ID,
52
+ provePortableSourceReconciliation,
53
+ projectPortableSourceReconciliationProofSummary,
54
+ qualifyPortableSourceReconciliationProofForManufacture
55
+ } from '../tooling/portable/comparison/sourceFrontierReconciliationProof.js';
@@ -1,3 +1,4 @@
1
1
  export * from './index.js';
2
2
  export * from '../tooling/portable/index.js';
3
3
  export * from '../tooling/portable/adapters/node/sourceFrontierComparison.js';
4
+ export * from '../tooling/portable/adapters/node/sourceFrontierReconciliationProof.js';
@@ -179,7 +179,7 @@ export function validateArtifactCreationResult(draft = {}, parentRecord = {}, op
179
179
  findings.push(...(representation.findings || []).map((message, index) => error(`creation.continuation-representation.${index + 1}`, message)));
180
180
  findings.push(...validateParentTargetIntegrity(parsed, parentRecord, parentIntegrityTarget));
181
181
  const parentSchemaAuthority = parentRecord.schemaReferenceAuthority || {};
182
- const parentSchemaReference = qualifySchemaReferenceValue(parent.schema?.raw || '', parentSchemaAuthority);
182
+ const parentSchemaReference = qualifySchemaReferenceValue(parent.schema?.raw || '', parentSchemaAuthority, { requireExactTargetWhenQualified: true });
183
183
  for (const [index, message] of (parentSchemaReference.findings || []).entries()) findings.push(error(`creation.parent-schema-reference.${index + 1}`, message));
184
184
  if (parentSchemaAuthority.resolutionState !== 'qualified') findings.push(error('creation.parent-schema-reference.unresolved', 'Exact continuation requires the declared Parent Schema reference authority to be resolver-qualified.'));
185
185
  } else if (!rootCreation) findings.push(error('creation.parent.required', 'Continuation creation requires an exact supplied Parent authority.'));
@@ -3,13 +3,9 @@ import { inspectCreationRepresentation } from './creation.representation.js';
3
3
  import { qualifySchemaReferenceMaterialCoherence, qualifySchemaReferenceValue, schemaReferenceAuthorityFromBinding } from './schema.reference.js';
4
4
 
5
5
  export function schemaReferenceAuthoritiesForCreation(targetModule = null, explicit = null) {
6
- const root = resolveRegisteredSchemaModule({ schemaId: 'tiinex.root.v1' });
7
- const rootModule = root?.fallbackUsed ? null : root?.module || null;
8
- const rootQualification = qualifiedSourceQualification(rootModule);
9
- const targetQualification = qualifiedSourceQualification(targetModule);
10
6
  const defaults = {
11
- envelope: schemaReferenceAuthorityFromBinding('tiinex.root.v1', rootModule?.binding || {}, rootQualification?.authority || null, rootQualification),
12
- current: schemaReferenceAuthorityFromBinding(String(targetModule?.id || ''), targetModule?.binding || {}, targetQualification?.authority || null, targetQualification)
7
+ envelope: schemaReferenceAuthorityForRegisteredSchema('tiinex.root.v1'),
8
+ current: schemaReferenceAuthorityForModule(targetModule)
13
9
  };
14
10
  return Object.freeze({
15
11
  envelope: explicitSchemaReferenceAuthority(explicit?.envelope, defaults.envelope),
@@ -17,6 +13,19 @@ export function schemaReferenceAuthoritiesForCreation(targetModule = null, expli
17
13
  });
18
14
  }
19
15
 
16
+ export function schemaReferenceAuthorityForRegisteredSchema(schemaId = '') {
17
+ const id = String(schemaId || '').trim();
18
+ const resolution = resolveRegisteredSchemaModule({ schemaId: id });
19
+ const module = resolution?.fallbackUsed ? null : resolution?.module || null;
20
+ return schemaReferenceAuthorityForModule(module, id);
21
+ }
22
+
23
+ function schemaReferenceAuthorityForModule(module = null, fallbackSchemaId = '') {
24
+ const id = String(module?.id || fallbackSchemaId || '').trim();
25
+ const qualification = qualifiedSourceQualification(module);
26
+ return schemaReferenceAuthorityFromBinding(id, module?.binding || {}, qualification?.authority || null, qualification);
27
+ }
28
+
20
29
  export function explicitSchemaReferenceAuthority(value = null, fallback = {}) {
21
30
  if (!value) return fallback;
22
31
  const schemaId = String(value.schemaId || fallback.schemaId || '').trim();
@@ -27,7 +36,7 @@ export function explicitSchemaReferenceAuthority(value = null, fallback = {}) {
27
36
  const preferredTarget = String(value.preferredTarget || value.target || exactTargets[0] || '').trim();
28
37
  const fallbackTargets = Object.freeze([...(fallback.exactTargets || [])]);
29
38
  const materialBoundTarget = Boolean(preferredTarget && fallback?.materialBoundTarget === true && fallbackTargets.includes(preferredTarget));
30
- return Object.freeze({
39
+ const candidate = Object.freeze({
31
40
  ...fallback,
32
41
  schemaId,
33
42
  exactTargets: Object.freeze(exactTargets),
@@ -39,6 +48,17 @@ export function explicitSchemaReferenceAuthority(value = null, fallback = {}) {
39
48
  materialBoundTarget,
40
49
  semanticMaterialIdentity: fallback.semanticMaterialIdentity || Object.freeze({ state: 'unavailable' })
41
50
  });
51
+ if (!preferredTarget) return candidate;
52
+ const coherence = qualifySchemaReferenceMaterialCoherence(candidate);
53
+ if (coherence.state === 'qualified') return candidate;
54
+ return Object.freeze({
55
+ ...candidate,
56
+ exactTargets: Object.freeze([]),
57
+ preferredTarget: '',
58
+ resolutionState: 'unresolved',
59
+ rejectedExactTargets: Object.freeze(exactTargets),
60
+ resolutionFindings: Object.freeze([...(coherence.findings || [])])
61
+ });
42
62
  }
43
63
 
44
64
  export function qualifyCreationSchemaReferences(markdown = '', contract = {}) {
@@ -53,7 +73,7 @@ export function qualifyCreationSchemaReferences(markdown = '', contract = {}) {
53
73
  }
54
74
 
55
75
  function qualifyResolvedSchemaReference(value, authority) {
56
- const lexical = qualifySchemaReferenceValue(value, authority);
76
+ const lexical = qualifySchemaReferenceValue(value, authority, { requireExactTargetWhenQualified: true });
57
77
  const material = qualifySchemaReferenceMaterialCoherence(authority);
58
78
  const findings = [...(lexical.findings || []), ...(material.findings || [])];
59
79
  return Object.freeze({ ...lexical, state: findings.length ? 'unavailable' : 'qualified', resolutionState: authority?.resolutionState || '', materialCoherence: material, findings: Object.freeze(findings) });
@@ -6,13 +6,14 @@ export const SCHEMA_REFERENCE_MATERIAL_COHERENCE_SCHEMA_ID = 'tiinex.site.schema
6
6
  export function schemaReferenceAuthorityFromBinding(schemaId = '', binding = {}, sourceAuthority = null, sourceQualification = null) {
7
7
  const id = String(schemaId || binding?.schemaId || '').trim();
8
8
  const localUnpublished = String(binding?.publicationState || '').toLowerCase().includes('unpublished');
9
- const exactSourceTargets = localUnpublished
9
+ const referencePublished = qualifiedImmutableReferencePublication(binding, sourceQualification);
10
+ const exactSourceTargets = localUnpublished && !referencePublished
10
11
  ? Object.freeze({ state: 'unavailable', targets: Object.freeze([]), findings: Object.freeze(['Binding is explicitly unpublished; remote source targets are not creation reference authority.']) })
11
12
  : canonicalGithubSchemaSourceTargets(sourceAuthority);
12
13
  const exactTargets = exactSourceTargets.state === 'qualified' ? [...exactSourceTargets.targets] : [];
13
14
  const semanticMaterialIdentity = normalizeMaterialIdentity(sourceQualification?.materialIdentity || {});
14
15
  const sourceQualified = sourceQualification?.state === 'qualified' && semanticMaterialIdentity.state === 'qualified';
15
- const materialBoundTarget = Boolean(exactTargets.length && sourceQualified && sourceQualification?.bindingMaterialCoherence?.state === 'qualified');
16
+ const materialBoundTarget = Boolean(exactTargets.length && sourceQualified && (sourceQualification?.bindingMaterialCoherence?.state === 'qualified' || referencePublished));
16
17
  const preferredTarget = exactTargets[0] || '';
17
18
  return Object.freeze({
18
19
  schema: SCHEMA_REFERENCE_AUTHORITY_SCHEMA_ID,
@@ -29,6 +30,17 @@ export function schemaReferenceAuthorityFromBinding(schemaId = '', binding = {},
29
30
  });
30
31
  }
31
32
 
33
+ function qualifiedImmutableReferencePublication(binding = {}, sourceQualification = null) {
34
+ if (String(binding?.schemaReferencePublicationState || '').trim() !== 'published-immutable-canonical') return false;
35
+ if (sourceQualification?.state !== 'qualified' || sourceQualification?.materialIdentity?.state !== 'qualified') return false;
36
+ if (sourceQualification?.authority?.providerQualification?.state !== 'qualified') return false;
37
+ const bindingBlobSha = String(binding?.sourceBlobSha || '').trim().toLowerCase();
38
+ const loadedBlobSha = String(sourceQualification?.materialIdentity?.sourceBlobSha || '').trim().toLowerCase();
39
+ const expectedChecksum = String(binding?.checksum?.value || binding?.checksum || '').trim().toLowerCase();
40
+ const loadedChecksum = String(sourceQualification?.materialIdentity?.sha256 || '').trim().toLowerCase();
41
+ return Boolean(bindingBlobSha && loadedBlobSha && bindingBlobSha === loadedBlobSha && expectedChecksum && loadedChecksum && expectedChecksum === loadedChecksum);
42
+ }
43
+
32
44
  export function qualifySchemaReferenceMaterialCoherence(authority = {}) {
33
45
  const semantic = normalizeMaterialIdentity(authority?.semanticMaterialIdentity || {});
34
46
  const target = String(authority?.preferredTarget || authority?.target || '').trim();
@@ -105,14 +117,24 @@ export function parseSchemaReferenceValue(value = '') {
105
117
  export function renderSchemaReference(authority = {}) {
106
118
  const schemaId = String(authority?.schemaId || '').trim();
107
119
  if (!schemaId) throw new Error('schema-reference-id-missing');
108
- const target = String(authority?.preferredTarget || '').trim();
120
+ const target = qualifiedExactSchemaReferenceTarget(authority);
109
121
  return target ? `[${schemaId}](${target})` : schemaId;
110
122
  }
111
123
 
112
- export function qualifySchemaReferenceValue(value = '', authority = {}) {
124
+ export function qualifiedExactSchemaReferenceTarget(authority = {}) {
125
+ const preferredTarget = String(authority?.preferredTarget || '').trim();
126
+ const exactTargets = new Set([...(authority?.exactTargets || [])].map((item) => String(item ?? '')).filter(Boolean));
127
+ if (String(authority?.resolutionState || authority?.state || '') !== 'qualified') return '';
128
+ if (!preferredTarget || !exactTargets.has(preferredTarget)) return '';
129
+ return preferredTarget;
130
+ }
131
+
132
+ export function qualifySchemaReferenceValue(value = '', authority = {}, options = {}) {
113
133
  const observed = parseSchemaReferenceValue(value);
114
134
  const expectedSchemaId = String(authority?.schemaId || '').trim();
115
135
  const exactTargets = new Set([...(authority?.exactTargets || [])].map((item) => String(item ?? '')).filter(Boolean));
136
+ const qualifiedExactTarget = qualifiedExactSchemaReferenceTarget(authority);
137
+ const requireExactTargetWhenQualified = options?.requireExactTargetWhenQualified === true;
116
138
  const findings = [];
117
139
  if (!expectedSchemaId) findings.push('Expected schema identifier authority is unavailable.');
118
140
  if (observed.schemaId !== expectedSchemaId) findings.push(`Schema identifier must be exactly ${expectedSchemaId}; observed ${observed.schemaId || '(empty)'}.`);
@@ -120,12 +142,16 @@ export function qualifySchemaReferenceValue(value = '', authority = {}) {
120
142
  if (observed.form === 'markdown-link') {
121
143
  targetState = exactTargets.has(observed.target) ? 'qualified' : 'unqualified';
122
144
  if (targetState !== 'qualified') findings.push(`Schema reference target is not qualified for ${expectedSchemaId}: ${observed.target || '(empty)'}.`);
145
+ } else if (observed.form === 'plain-schema-id' && requireExactTargetWhenQualified && qualifiedExactTarget) {
146
+ targetState = 'qualified-exact-target-omitted';
147
+ findings.push(`Qualified exact schema-reference authority requires Markdown Link form targeting ${qualifiedExactTarget}; a plain schema id is not sufficient for this new candidate.`);
123
148
  }
124
149
  if (observed.form === 'empty') findings.push('Schema reference value is empty.');
125
150
  return Object.freeze({
126
151
  state: findings.length ? 'unavailable' : 'qualified',
127
152
  schemaIdState: observed.schemaId === expectedSchemaId && expectedSchemaId ? 'qualified' : 'unavailable',
128
153
  targetState,
154
+ qualifiedExactTarget,
129
155
  observed,
130
156
  authority,
131
157
  findings: Object.freeze(findings)
@@ -20,5 +20,6 @@
20
20
  "bindingVersion": "tiinex.web.schema-binding.v1",
21
21
  "originId": "tiinex-docs-39889512-accepted-local",
22
22
  "originTrustRole": "canonical-core",
23
- "publicationState": "accepted-local-unpublished"
23
+ "publicationState": "accepted-local-unpublished",
24
+ "schemaReferencePublicationState": "published-immutable-canonical"
24
25
  }
@@ -11,6 +11,7 @@ import { reductionPreflightCliInput } from './cli.reduction-input.js';
11
11
  import {land} from './cli.land.js';
12
12
  import { OPERATIONS_WITHOUT_EXPLICIT_MATERIAL } from './cli.material-policy.js';
13
13
  import { prepareSourceFrontierComparisonCliInput } from './cli.source-frontier-comparison.js';
14
+ import { prepareSourceFrontierReconciliationCliInput } from './cli.source-frontier-reconciliation.js';
14
15
 
15
16
  export async function commandInput(parsed, runtime = {}) {
16
17
  const flags = parsed.flags;
@@ -140,6 +141,10 @@ export async function commandInput(parsed, runtime = {}) {
140
141
  }
141
142
 
142
143
  if (parsed.command === 'compare-source-frontiers') return prepareSourceFrontierComparisonCliInput(parsed, flags);
144
+ if (parsed.command === 'prove-source-reconciliation') {
145
+ const dispositions = await readOptionalJson(flags.dispositions || flags['disposition-file']);
146
+ return prepareSourceFrontierReconciliationCliInput(flags, dispositions);
147
+ }
143
148
 
144
149
  if (parsed.command === 'manufacture-handoff-package') {
145
150
  return prepareHandoffManufactureCliCommand(parsed, runtime);
@@ -1,4 +1,5 @@
1
1
  import { projectPortableSourceFrontierComparisonSummary } from '../../comparison/sourceFrontierComparison.js';
2
+ import { projectPortableSourceReconciliationProofSummary } from '../../comparison/sourceFrontierReconciliationProof.js';
2
3
 
3
4
  const COMMON_DEFAULT_PROJECTION = 'common-default';
4
5
 
@@ -8,9 +9,31 @@ export function projectCommonCliDefaultOutput(result = {}, parsed = {}) {
8
9
  if (parsed?.command === 'project-grounding-readiness') return projectGroundDefault(result, parsed);
9
10
  if (parsed?.command === 'manufacture-handoff-package' && parsed?.surfaceCommand === 'handoff') return projectHandoffDefault(result, parsed);
10
11
  if (parsed?.command === 'compare-source-frontiers') return projectCompareDefault(result, parsed);
12
+ if (parsed?.command === 'prove-source-reconciliation') return projectReconciliationProofDefault(result, parsed);
11
13
  return result;
12
14
  }
13
15
 
16
+ function projectReconciliationProofDefault(result = {}, parsed = {}) {
17
+ const summary = projectPortableSourceReconciliationProofSummary({ ...result, schema: result.resultSchema || result.schema || '' }, { maxPaths: parsed?.flags?.['max-paths'] || 20 });
18
+ return Object.freeze({
19
+ schema: result.schema,
20
+ operation: result.operation || 'prove-source-reconciliation',
21
+ resultSchema: result.resultSchema,
22
+ projection: COMMON_DEFAULT_PROJECTION,
23
+ status: summary.status,
24
+ state: summary.state,
25
+ counts: summary.counts,
26
+ preservation: summary.preservation,
27
+ workspaces: summary.workspaces,
28
+ proofFingerprint: summary.proofFingerprint,
29
+ manufactureBinding: summary.manufactureBinding,
30
+ findingSummary: summary.findingSummary,
31
+ actionableFindings: summary.actionableFindings,
32
+ detail: Object.freeze({ fullReceipt: Object.freeze({ command: String(parsed.surfaceCommand || 'reconcile'), flag: '--full', requiredForManufacture: true }) }),
33
+ boundary: summary.boundary
34
+ });
35
+ }
36
+
14
37
  function projectCompareDefault(result = {}, parsed = {}) {
15
38
  const summary = projectPortableSourceFrontierComparisonSummary({ ...result, schema: result.resultSchema || result.schema || '' }, { maxPaths: parsed?.flags?.['max-paths'] || 20 });
16
39
  return Object.freeze({
@@ -28,6 +28,8 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
28
28
  if (!flags.route && handoffPath) flags.route = handoffPath;
29
29
  if (!flags.output && !flags['output-dir'] && parsed.surfaceCommand === 'handoff' && continuationState.returnOutputDir) flags['output-dir'] = continuationState.returnOutputDir;
30
30
  const materialBindings = await readOptionalJson(flags['material-bindings'] || flags.materials);
31
+ const reconciliationProof = await readOptionalJson(flags['reconciliation-proof']);
32
+ const requireReconciliationProof = Boolean(flags['require-reconciliation-proof']);
31
33
  const packageParentWorkspaceIds = splitFlag(flags['package-parent-workspaces']);
32
34
  const packageParentWorkspaceAliases = await readOptionalJson(flags['package-parent-workspace-aliases']);
33
35
  const operatorCarrierProfile = await readOptionalJson(flags['carrier-profile']);
@@ -129,7 +131,9 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
129
131
  packageParentPath: parentPackagePath ? path.resolve(parentPackagePath) : '',
130
132
  packageParentSha256,
131
133
  packageParentWorkspaceIds,
132
- packageParentWorkspaceAliases
134
+ packageParentWorkspaceAliases,
135
+ reconciliationProof,
136
+ requireReconciliationProof
133
137
  }, runtime);
134
138
  return {
135
139
  input,
@@ -13,6 +13,7 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
13
13
  `${command} author <workspace-dir> --schema <schema-id> (--path <workspace-relative-artifact> | --directory <workspace-relative-directory>) --body <body.md> [--parent <workspace-relative-or-qualified-parent>] [--parent-source <local-parent-file>] [--title <title>] [--summary <summary>] [--why <why>]`,
14
14
  `${command} handoff <workspace-dir>`,
15
15
  `${command} compare --left-kind <kind> --left <path> --right-kind <kind> --right <path> [side selectors]`,
16
+ `${command} reconcile --base-kind <kind> --base <path> --incoming-kind <kind> --incoming <path> --current-kind <kind> --current <path> --reconciled-kind <kind> --reconciled <path> [--dispositions <json-file>]`,
16
17
  '',
17
18
  `Handoff aliases: ${command} orient <carrier.zip>; ${command} validate <carrier.zip>`,
18
19
  `Advanced/internal catalog: ${command} operations`,
@@ -21,7 +22,7 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
21
22
  '- `orient`, `ground`, and public `handoff` use compact decision-first default projections; add `--full` on the same command for the complete qualified receipt.',
22
23
  '- `ground` is read-only; append `--continue <workspace-dir>` only after `grounded-to-act` to materialize the selected carried Workspace and runtime-only `.tiinex/continuation.json`.',
23
24
  '- `author` uses continuation state to infer ordinary Parent continuity, seal c14n-v2 integrity, audit, and stage; invalid artifacts are not retained.',
24
- '- `handoff` uses continuation state plus the latest qualified authored Handoff to manufacture the canonical return carrier and excludes runtime-only `.tiinex` state. A received `--package-parent` continues carrier lineage only; it never selects parent Workspace source by itself. Advanced manufacture may opt into exact parent snapshots with `--package-parent-workspaces <id,...|all>` and may bind an explicitly selected renamed predecessor with `--package-parent-workspace-aliases`; aliases never infer source identity. Normal operator completion is one Handoff package plus the exact routing text; markdown-capable hosts render that routing in a fenced code block, and do not emit loose Evidence/Handoff markdown as extra transport payloads.',
25
+ '- `handoff` uses continuation state plus the latest qualified authored Handoff to manufacture the canonical return carrier and excludes runtime-only `.tiinex` state. A received `--package-parent` continues carrier lineage only; it never selects parent Workspace source by itself. Advanced manufacture may opt into exact parent snapshots with `--package-parent-workspaces <id,...|all>` and may bind an explicitly selected renamed predecessor with `--package-parent-workspace-aliases`; aliases never infer source identity. Recovery/integration manufacture may require a full `reconcile --full` receipt with `--require-reconciliation-proof --reconciliation-proof <receipt.json>`; manufacture re-enumerates source and fails closed on missing/stale proof. Normal operator completion is one Handoff package plus the exact routing text; markdown-capable hosts render that routing in a fenced code block, and do not emit loose Evidence/Handoff markdown as extra transport payloads.',
25
26
  '- Remote reads/writes remain explicit host concerns. Tooling operation safety does not create or revoke semantic Task/Handoff authority.',
26
27
  '',
27
28
  'Use `<common-command> --help` for focused common-path usage. Use `operations` deliberately for the advanced/internal operation catalog.'
@@ -29,6 +30,18 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
29
30
  }
30
31
 
31
32
  function commonCommandHelp(command, surfaceCommand) {
33
+ if (surfaceCommand === 'reconcile' || surfaceCommand === 'prove-source-reconciliation') return [
34
+ 'Tiinex portable tooling — prove source reconciliation for manufacture',
35
+ '',
36
+ `${command} reconcile --base-kind <local-workspace|local-frontier|handoff-package> --base <path> --incoming-kind <kind> --incoming <path> --current-kind <kind> --current <path> --reconciled-kind <kind> --reconciled <path> [side ids/selectors/roots] [--dispositions <json-file>]`,
37
+ '',
38
+ 'The gate deterministically classifies exact, incoming-only, current-only, same-result-concurrent, deletion-candidate, and conflicting-overlap paths. Deletion/conflict candidates remain blocked until an explicit disposition selects `incoming`, `current`, `base`, `reconciled`, or `delete`. `reconciled` explicitly accepts the candidate bytes at that path and therefore supports a separately decided manual semantic resolution without Tooling deciding that resolution.',
39
+ 'The candidate reconciled frontier must preserve every automatically accepted incoming-only/current-only source identity and match every explicit disposition. Extra ungrounded candidate paths fail closed. Local source uses the same enumeration/exclusion contract as Handoff manufacture.',
40
+ 'Default output is compact. Use `--full` when saving a proof for `handoff --require-reconciliation-proof --reconciliation-proof <receipt.json>`; manufacture re-enumerates the Workspace and rejects stale or mismatched candidate bytes.',
41
+ 'This is mechanical byte/path proof only: no semantic merge correctness, intentional deletion, authority, acceptance, remote acquisition, or source mutation is inferred.',
42
+ '',
43
+ `Advanced/internal catalog: ${command} operations`
44
+ ];
32
45
  if (surfaceCommand === 'compare' || surfaceCommand === 'compare-source-frontiers') return [
33
46
  'Tiinex portable tooling — compare source frontiers',
34
47
  '',
@@ -70,7 +83,7 @@ function commonCommandHelp(command, surfaceCommand) {
70
83
  '',
71
84
  `${command} handoff <workspace-dir>`,
72
85
  '',
73
- 'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. A non-major continuation that writes a carrier must supply an explicitly coordinated `--package-sibling-index <1..9999>`; Tooling will not silently discover the next sibling because isolated runtimes cannot prove a globally unique reservation. Distinct explicit sibling indexes preserve same-Major parallelism, while byte-identical duplicate transport at one exact output path is idempotent and divergent bytes fail closed. It does not implicitly carry sibling Workspaces from the received package; advanced manufacture must select exact reusable parent snapshots with `--package-parent-workspaces <id,...|all>`. The default receipt keeps output identity, routing text, closure/workspace qualification, verification, and actionable findings compact; add `--full` for the complete manufacture receipt. Normal operator completion is exactly one Handoff package plus the adjacent exact routing text. In markdown-capable hosts render that routing in a fenced code block; do not emit canonical Workspace Evidence/Handoff markdown as additional loose transport files. Runtime-only `.tiinex` state is excluded from canonical manufacture.',
86
+ 'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. A non-major continuation that writes a carrier must supply an explicitly coordinated `--package-sibling-index <1..9999>`; Tooling will not silently discover the next sibling because isolated runtimes cannot prove a globally unique reservation. Distinct explicit sibling indexes preserve same-Major parallelism, while byte-identical duplicate transport at one exact output path is idempotent and divergent bytes fail closed. It does not implicitly carry sibling Workspaces from the received package; advanced manufacture must select exact reusable parent snapshots with `--package-parent-workspaces <id,...|all>`. Recovery/integration manufacture can bind the reconciled byte proof with `--require-reconciliation-proof --reconciliation-proof <full-reconcile-receipt.json>`; missing, non-ready, edited, or source-stale proof blocks package output. The default receipt keeps output identity, routing text, closure/workspace qualification, verification, and actionable findings compact; add `--full` for the complete manufacture receipt. Normal operator completion is exactly one Handoff package plus the adjacent exact routing text. In markdown-capable hosts render that routing in a fenced code block; do not emit canonical Workspace Evidence/Handoff markdown as additional loose transport files. Runtime-only `.tiinex` state is excluded from canonical manufacture.',
74
87
  '',
75
88
  `Advanced/internal catalog: ${command} operations`
76
89
  ];
@@ -425,7 +425,7 @@ function withCliPhaseTiming(result = {}, timing = {}) {
425
425
  function parseArgs(argv=[]) {
426
426
  const args=[...argv],first=args.shift()||'';
427
427
  if(first==='--help'||first==='-h') return {command:'help',flags:{help:true},positionals:[]};
428
- const command=({orient:'orient-handoff-package',ground:'project-grounding-readiness',receive:'qualify-cold-start',validate:'audit-handoff-package-context',handoff:'manufacture-handoff-package',author:'author',compare:'compare-source-frontiers'})[first]||first;
428
+ const command=({orient:'orient-handoff-package',ground:'project-grounding-readiness',receive:'qualify-cold-start',validate:'audit-handoff-package-context',handoff:'manufacture-handoff-package',author:'author',compare:'compare-source-frontiers',reconcile:'prove-source-reconciliation'})[first]||first;
429
429
  const flags={},positionals=[];
430
430
  while(args.length){const token=args.shift();if(!token.startsWith('--')){positionals.push(token);continue;}const key=token.slice(2);flags[key]=!args.length||args[0].startsWith('--')?true:args.shift();}
431
431
  return {command,flags,positionals,surfaceCommand:first};
@@ -9,20 +9,20 @@ export async function prepareSourceFrontierComparisonCliInput(parsed = {}, flags
9
9
  };
10
10
  if (threeWay) {
11
11
  const request = {
12
- base: descriptorFromFlags('base', flags),
13
- incoming: descriptorFromFlags('incoming', flags),
14
- current: descriptorFromFlags('current', flags)
12
+ base: sourceFrontierDescriptorFromFlags('base', flags),
13
+ incoming: sourceFrontierDescriptorFromFlags('incoming', flags),
14
+ current: sourceFrontierDescriptorFromFlags('current', flags)
15
15
  };
16
16
  return { input: await prepareNodeSourceFrontierComparisonInput(request, descriptorOptions), options: {} };
17
17
  }
18
18
  const request = {
19
- left: descriptorFromFlags('left', flags),
20
- right: descriptorFromFlags('right', flags)
19
+ left: sourceFrontierDescriptorFromFlags('left', flags),
20
+ right: sourceFrontierDescriptorFromFlags('right', flags)
21
21
  };
22
22
  return { input: await prepareNodeSourceFrontierComparisonInput(request, descriptorOptions), options: {} };
23
23
  }
24
24
 
25
- function descriptorFromFlags(prefix, flags) {
25
+ export function sourceFrontierDescriptorFromFlags(prefix, flags) {
26
26
  const kind = String(flags[`${prefix}-kind`] || '').trim();
27
27
  const root = String(flags[prefix] || '').trim();
28
28
  const workspaceId = String(flags[`${prefix}-id`] || flags[`${prefix}-workspace-id`] || '').trim();
@@ -0,0 +1,18 @@
1
+ import { prepareNodeSourceReconciliationProofInput } from '../node/sourceFrontierReconciliationProof.js';
2
+ import { sourceFrontierDescriptorFromFlags } from './cli.source-frontier-comparison.js';
3
+
4
+ export async function prepareSourceFrontierReconciliationCliInput(flags = {}, dispositions = []) {
5
+ const descriptorOptions = {
6
+ maxFiles: flags['max-files'],
7
+ maxCarrierFiles: flags['max-carrier-files'],
8
+ maxTextBytes: flags['max-text-bytes']
9
+ };
10
+ const request = {
11
+ base: sourceFrontierDescriptorFromFlags('base', flags),
12
+ incoming: sourceFrontierDescriptorFromFlags('incoming', flags),
13
+ current: sourceFrontierDescriptorFromFlags('current', flags),
14
+ reconciled: sourceFrontierDescriptorFromFlags('reconciled', flags),
15
+ dispositions: Array.isArray(dispositions) ? dispositions : dispositions?.dispositions || []
16
+ };
17
+ return { input: await prepareNodeSourceReconciliationProofInput(request, descriptorOptions), options: {} };
18
+ }
@@ -14,6 +14,7 @@ export async function buildToolingBootstrapTransportFiles(input = {}) {
14
14
  const delivery = normalizeDelivery(input.delivery);
15
15
  const runtimeRoot = path.resolve(String(input.runtimeRoot || DEFAULT_RUNTIME_ROOT));
16
16
  const runtime = await enumerateRuntimeDependencyGraph(runtimeRoot, { maxFiles: input.maxFiles });
17
+ const runtimeIdentity = runtimeIdentityFromEnumeration(runtime);
17
18
  const manifest = Object.freeze({
18
19
  schema: PORTABLE_TOOLING_BOOTSTRAP_MANIFEST_SCHEMA_ID,
19
20
  version: 1,
@@ -30,7 +31,42 @@ export async function buildToolingBootstrapTransportFiles(input = {}) {
30
31
  const summary = Object.freeze({ schema: 'tiinex.portable.tooling-bootstrap.summary.v1', delivery, manifestSha256, representationSha256: runtime.representationSha256, runtimeFiles: runtime.entries.length, runtimeBytes: runtime.totalBytes, status: delivery === 'embedded' ? 'embedded-qualified' : 'persistent-identity-verified', persistentVerification });
31
32
  const files = [transportFile('tiinex.bootstrap/manifest.json', manifestBytes, 'tooling-bootstrap-manifest', 'portable-tooling-bootstrap-control')];
32
33
  if (delivery === 'embedded') for (const entry of runtime.entries) files.push(transportFile(`tiinex.bootstrap/runtime/${entry.path}`, entry.data, 'tooling-bootstrap-runtime', 'portable-tooling-bootstrap-runtime'));
33
- return Object.freeze({ manifest, summary, files: Object.freeze(files) });
34
+ return Object.freeze({ manifest, summary, files: Object.freeze(files), runtimeIdentity });
35
+ }
36
+
37
+ export async function buildToolingBootstrapRuntimeIdentity(input = {}) {
38
+ const runtimeRoot = path.resolve(String(input.runtimeRoot || DEFAULT_RUNTIME_ROOT));
39
+ const runtime = await enumerateRuntimeDependencyGraph(runtimeRoot, { maxFiles: input.maxFiles });
40
+ return runtimeIdentityFromEnumeration(runtime);
41
+ }
42
+
43
+
44
+ function runtimeIdentityFromEnumeration(runtime = {}) {
45
+ const runtimeEntries = runtime.entries || [];
46
+ const packageEntry = runtimeEntries.find((entry) => String(entry?.path || '') === 'package.json');
47
+ let packageName = '';
48
+ let packageVersion = '';
49
+ if (packageEntry?.data) {
50
+ try {
51
+ const parsed = JSON.parse(new TextDecoder().decode(packageEntry.data));
52
+ packageName = String(parsed?.name || '').trim();
53
+ packageVersion = String(parsed?.version || '').trim();
54
+ } catch {}
55
+ }
56
+ const shaFor = (entryPath) => String(runtimeEntries.find((entry) => String(entry?.path || '') === entryPath)?.sha256 || '');
57
+ const sourceEntries = runtimeEntries.filter((entry) => String(entry?.path || '') !== 'package.json');
58
+ const sourceRepresentationSha256 = sha256Text(stableJson(sourceEntries.map(({ path: entryPath, bytes, sha256 }) => ({ path: `runtime/${entryPath}`, bytes, sha256 }))));
59
+ return Object.freeze({
60
+ schema: 'tiinex.portable.tooling-runtime-identity.v1',
61
+ packageName,
62
+ packageVersion,
63
+ representationSha256: String(runtime.representationSha256 || ''),
64
+ sourceRepresentationSha256,
65
+ runtimeFiles: Number(runtimeEntries.length),
66
+ runtimeBytes: Number(runtime.totalBytes || 0),
67
+ entrypointSha256: shaFor('tools/tiinex-portable.mjs'),
68
+ enumerationPolicySha256: shaFor('src/tooling/portable/adapters/node/handoff.manufacture.enumeration.js')
69
+ });
34
70
  }
35
71
 
36
72
  function verifyExpectedPersistentBootstrap(expected, manifest, manifestSha256) {
@@ -2,17 +2,25 @@ import { readdir, readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { sha256Hex } from '../../../../export/package.bytes.js';
4
4
  import { safeWorkspaceToken, serializableMetadata } from './handoff.manufacture.multiRoot.js';
5
+ import {
6
+ DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES,
7
+ DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS,
8
+ portableSourceEligibilityPolicy,
9
+ qualifyPortableSourcePath
10
+ } from '../../source/sourceEligibility.js';
5
11
 
6
12
  export const PORTABLE_NODE_WORKSPACE_ENUMERATION_SCHEMA_ID = 'tiinex.portable.node-workspace-enumeration.v1';
7
- export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES = Object.freeze(['.git', '.tiinex', 'node_modules', '.site-publish', '.release', '.outgoing-handoff-packages']);
8
- export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS = Object.freeze(['.vscode/link']);
13
+ export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES = DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES;
14
+ export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS = DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS;
9
15
  const DEFAULT_MAX_FILES = 10000;
10
16
 
11
17
  export async function enumerateNodeWorkspace(rootInput = '.', options = {}) {
12
18
  const root = path.resolve(String(rootInput || '.'));
13
19
  const maxFiles = positiveInteger(options.maxFiles, DEFAULT_MAX_FILES);
14
- const excluded = new Set([...(options.excludeDirectories || DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES)].map(String));
15
- const excludedPaths = new Set(options.excludeRelativePaths || DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS);
20
+ const sourceEligibility = portableSourceEligibilityPolicy({
21
+ excludeDirectories: options.excludeDirectories == null ? DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES : options.excludeDirectories,
22
+ excludeRelativePaths: options.excludeRelativePaths == null ? DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS : options.excludeRelativePaths
23
+ });
16
24
  const queue = [root];
17
25
  const absoluteFiles = [];
18
26
  const skippedSymlinks = [];
@@ -21,10 +29,15 @@ export async function enumerateNodeWorkspace(rootInput = '.', options = {}) {
21
29
  const entries = await readdir(current, { withFileTypes: true });
22
30
  entries.sort((a, b) => a.name.localeCompare(b.name));
23
31
  for (const entry of entries) {
24
- if (entry.isDirectory() && excluded.has(entry.name)) continue;
25
32
  const absolute = path.join(current, entry.name);
26
- if (excludedPaths.has(normalizeRelativePath(path.relative(root, absolute)))) continue;
27
- if (entry.isSymbolicLink()) { skippedSymlinks.push(normalizeRelativePath(path.relative(root, absolute))); continue; }
33
+ const relative = normalizeRelativePath(path.relative(root, absolute));
34
+ const eligibility = qualifyPortableSourcePath(relative, {
35
+ excludeDirectories: sourceEligibility.excludedDirectories,
36
+ excludeRelativePaths: sourceEligibility.excludedRelativePaths,
37
+ entryKind: entry.isDirectory() ? 'directory' : 'file'
38
+ });
39
+ if (!eligibility.eligible) continue;
40
+ if (entry.isSymbolicLink()) { skippedSymlinks.push(relative); continue; }
28
41
  if (entry.isDirectory()) queue.push(absolute);
29
42
  else if (entry.isFile()) absoluteFiles.push(absolute);
30
43
  if (absoluteFiles.length > maxFiles) {
@@ -62,7 +75,13 @@ export async function enumerateNodeWorkspace(rootInput = '.', options = {}) {
62
75
  workspaceId,
63
76
  entryCount: includedEntries.length,
64
77
  totalBytes,
65
- exclusions: Object.freeze({ directories: Object.freeze([...excluded].sort()), relativePaths: Object.freeze([...excludedPaths].sort()), symbolicLinks: 'excluded-and-reported' }),
78
+ exclusions: Object.freeze({
79
+ directories: sourceEligibility.excludedDirectories,
80
+ relativePaths: sourceEligibility.excludedRelativePaths,
81
+ fileSuffixes: sourceEligibility.excludedFileSuffixes,
82
+ symbolicLinks: 'excluded-and-reported',
83
+ sourceEligibilitySchema: sourceEligibility.schema
84
+ }),
66
85
  skippedSymlinks: Object.freeze(skippedSymlinks.sort()),
67
86
  entriesFingerprint: sha256Text(stableJson(includedEntries))
68
87
  });