@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
@@ -15,8 +15,14 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
15
15
  carrierProfile: input.carrierProfile || null
16
16
  }, input.carrierLineage || upgraded.carrierProjection?.lineage || baseline.carrierProjection?.lineage || {});
17
17
  const majorFindings = majorReadiness.state === 'blocked' ? [Object.freeze({ severity: 'error', code: 'portable.handoff-carrier-lineage.major.not-self-contained', message: 'Major Handoff carrier requires complete replacement-capable carried Workspace snapshots.' })] : [];
18
+ const reconciliationProofQualification = input.reconciliationProofQualification || input.manufacturingEvidence?.reconciliationProof || null;
19
+ const reconciliationBlocked = String(reconciliationProofQualification?.state || '') === 'blocked';
20
+ const schemaReferencePreflight = input.schemaReferencePreflight || input.manufacturingEvidence?.schemaReferencePreflight || null;
21
+ const schemaReferenceBlocked = String(schemaReferencePreflight?.state || '') === 'blocked';
18
22
  const findings = Object.freeze([
19
23
  ...majorFindings,
24
+ ...(schemaReferencePreflight?.findings || []),
25
+ ...(reconciliationProofQualification?.findings || []),
20
26
  ...(baseline.findings || []),
21
27
  ...(upgraded.findings || []),
22
28
  ...(upgraded.inspection?.findings || []),
@@ -28,7 +34,7 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
28
34
  ...(upgraded.roundtrip?.findings || []),
29
35
  ...(toolingBootstrapInspection?.findings || [])
30
36
  ]);
31
- const status = baseline.status !== 'blocked' && upgraded.status !== 'blocked' && toolingBootstrapInspection?.status === 'valid' && majorReadiness.state !== 'blocked' ? upgraded.status : 'blocked';
37
+ const status = baseline.status !== 'blocked' && upgraded.status !== 'blocked' && toolingBootstrapInspection?.status === 'valid' && majorReadiness.state !== 'blocked' && !schemaReferenceBlocked && !reconciliationBlocked ? upgraded.status : 'blocked';
32
38
  return Object.freeze({
33
39
  schema: 'tiinex.portable.handoff-manufacturing.v2',
34
40
  status,
@@ -45,7 +51,9 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
45
51
  coldConsumerEntrypointInspection: String(upgraded.coldConsumerEntrypointInspection?.status || 'unavailable'),
46
52
  companionInspection: String(upgraded.companionInspection?.status || 'unavailable'),
47
53
  roundtrip: upgraded.roundtrip ? String(upgraded.roundtrip.status || 'unknown') : 'not-requested',
48
- toolingBootstrap: String(toolingBootstrapInspection?.status || 'unavailable')
54
+ toolingBootstrap: String(toolingBootstrapInspection?.status || 'unavailable'),
55
+ schemaReferencePreflight: String(schemaReferencePreflight?.state || 'not-run'),
56
+ reconciliationProof: String(reconciliationProofQualification?.state || 'not-required')
49
57
  }),
50
58
  plan: baseline.plan,
51
59
  bundle: upgraded.bundle || baseline.bundle,
@@ -63,6 +71,8 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
63
71
  roundtrip: upgraded.roundtrip || null,
64
72
  toolingBootstrap: input.toolingBootstrap || null,
65
73
  manufacturingEvidence: input.manufacturingEvidence || null,
74
+ schemaReferencePreflight,
75
+ reconciliationProofQualification,
66
76
  toolingBootstrapInspection,
67
77
  carrierLineage: upgraded.carrierProjection?.lineage || baseline.carrierProjection?.lineage || input.carrierLineage || null,
68
78
  majorReadiness,
@@ -73,12 +83,14 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
73
83
  sourceMutation: false,
74
84
  remoteMutation: false,
75
85
  physicalRoundtripVerification: upgraded.roundtrip ? String(upgraded.roundtrip.status || 'unknown') : 'not-requested',
86
+ schemaReferencePreflight: String(schemaReferencePreflight?.state || 'not-run'),
87
+ reconciliationProof: String(reconciliationProofQualification?.state || 'not-required'),
76
88
  hostBehaviorAuthority: 'none'
77
89
  }),
78
90
  migration: upgraded.migration || null,
79
91
  baseline: Object.freeze({ schema: baseline.schema, status: baseline.status, packageRepresentationSha256: String(baseline.bundle?.packageRepresentationSha256 || ''), representation: 'semantic-control-plus-detached-material-without-exploded-workspace-carrier' }),
80
92
  findings,
81
93
  findingSummary: summarizePortableFindings(findings),
82
- boundary: 'Canonical archive-backed Handoff manufacturing facade. It fails closed unless each carrier workspace is bound to one exact carried tiinex.workspace.v1 artifact and one independently verified complete workspace archive.'
94
+ boundary: 'Canonical archive-backed Handoff manufacturing facade. It fails closed unless each carrier workspace is bound to one exact carried tiinex.workspace.v1 artifact and one independently verified complete workspace archive; the actively selected local Handoff candidate must satisfy prospective per-field exact schema-reference authority, and when reconciliation proof is required/supplied, the exact source selected for manufacture must also match the qualified candidate reconciled frontier.'
83
95
  });
84
96
  }
@@ -1,6 +1,8 @@
1
1
  import { packageFileBytes, sha256Hex, utf8Bytes } from '../../../export/package.bytes.js';
2
2
  import { canonicalC14nV2SelfState, sealC14nV2Self } from '../../../integrity/integrity.c14nV2.js';
3
3
  import { C14N_V2_VALIDATOR_TARGET } from '../../../integrity/integrity.methodReference.js';
4
+ import { schemaReferenceAuthorityForRegisteredSchema } from '../../../schemas/creation.schemaReferences.js';
5
+ import { renderSchemaReference } from '../../../schemas/schema.reference.js';
4
6
  import { inspectHandoffCarrierProjection } from './carrierProjection.js';
5
7
  import { qualifyTiinexRouteArtifact } from './routeArtifactConformance.js';
6
8
 
@@ -8,6 +10,7 @@ export const HANDOFF_POINTER_ENTRYPOINT_PROJECTION_SCHEMA_ID = 'tiinex.portable.
8
10
  export const HANDOFF_POINTER_ENTRYPOINT_INSPECTION_SCHEMA_ID = 'tiinex.portable.handoff-pointer-entrypoints.inspection.v1';
9
11
  export const HANDOFF_POINTER_ENTRYPOINT_PREFIX = 'handoff-entrypoint-';
10
12
  export const CANONICAL_POINTER_SCHEMA_TARGET = 'https://github.com/Tiinex/docs/blob/3988951208eb9a8926e84ab42625d4b42fa00c2d/.topics/.schemas/core/pointer/tiinex.pointer.v1.schema.md';
13
+ const CANONICAL_ROOT_SCHEMA_REFERENCE = renderSchemaReference(schemaReferenceAuthorityForRegisteredSchema('tiinex.root.v1'));
11
14
 
12
15
  const BOUNDARY = 'Generated package-root tiinex.pointer.v1 orientation only. Pointer filename, prose, and placement have no Parent, assignment, acceptance, completion, source, package-identity, or route-selection authority; qualified package carrier/closure truth remains controlling.';
13
16
 
@@ -83,7 +86,7 @@ export function isHandoffPointerEntrypointPath(value = '') {
83
86
  function buildPointerEntry(route = {}, createdAt = '') {
84
87
  const path = pointerPath(route);
85
88
  const title = `Handoff route pointer — ${String(route.parties?.to || route.workspaceId || 'recipient')}`;
86
- const unsigned = `# Continuity Context\n\n- Envelope Schema: tiinex.root.v1\n- Current\n - Current Schema: [tiinex.pointer.v1](${CANONICAL_POINTER_SCHEMA_TARGET})\n - Created At: ${createdAt}\n - Summary: Thin package-local pointer to one qualified Handoff route.\n\n---\n\n# ${title}\n\nThis generated pointer exposes one next hop only. Package carrier and closure controls remain the authority for whether that Handoff route is qualified.\n\n## Destinations\n\n- Qualified Handoff route: [${route.workspaceRelativePath}](${route.pointerTarget || route.packagePath})\n\n# Continuity Integrity\n\n- [sha256-base64url-c14n-v2](${C14N_V2_VALIDATOR_TARGET})\n - Towards: self\n - Value: \n`;
89
+ const unsigned = `# Continuity Context\n\n- Envelope Schema: ${CANONICAL_ROOT_SCHEMA_REFERENCE}\n- Current\n - Current Schema: [tiinex.pointer.v1](${CANONICAL_POINTER_SCHEMA_TARGET})\n - Created At: ${createdAt}\n - Summary: Thin package-local pointer to one qualified Handoff route.\n\n---\n\n# ${title}\n\nThis generated pointer exposes one next hop only. Package carrier and closure controls remain the authority for whether that Handoff route is qualified.\n\n## Destinations\n\n- Qualified Handoff route: [${route.workspaceRelativePath}](${route.pointerTarget || route.packagePath})\n\n# Continuity Integrity\n\n- [sha256-base64url-c14n-v2](${C14N_V2_VALIDATOR_TARGET})\n - Towards: self\n - Value: \n`;
87
90
  const sealed = sealC14nV2Self(unsigned);
88
91
  if (sealed.state !== 'sealed') throw new Error(`portable.handoff-pointer.integrity.seal-failed:${sealed.reason || sealed.state}`);
89
92
  return deepFreeze({
@@ -0,0 +1,26 @@
1
+ import { runAudit } from '../../../audit/audit.run.js';
2
+ import { summarizePortableFindings } from '../findings.js';
3
+
4
+ export const PORTABLE_HANDOFF_SCHEMA_REFERENCE_PREFLIGHT_SCHEMA_ID = 'tiinex.portable.handoff-schema-reference-preflight.v1';
5
+
6
+ export function qualifyPortableManufactureSchemaReferenceCandidate(record = {}) {
7
+ const markdown = String(record.markdown || '');
8
+ const path = String(record.path || record.id || '');
9
+ const audit = runAudit({
10
+ record: Object.freeze({ ...record, path, markdown }),
11
+ markdown,
12
+ schemaReferenceContext: 'candidate'
13
+ });
14
+ const findings = Object.freeze((audit.findings || []).filter((finding) => String(finding?.code || '').startsWith('schema.reference.')));
15
+ const findingSummary = summarizePortableFindings(findings);
16
+ const blocked = Number(findingSummary?.counts?.error || 0) > 0;
17
+ return Object.freeze({
18
+ schema: PORTABLE_HANDOFF_SCHEMA_REFERENCE_PREFLIGHT_SCHEMA_ID,
19
+ state: blocked ? 'blocked' : 'qualified',
20
+ status: blocked ? 'blocked' : findingSummary?.counts?.warning ? 'degraded' : 'ready',
21
+ path,
22
+ findings,
23
+ findingSummary,
24
+ boundary: 'Prospective manufacture gate for the actively selected local Handoff candidate only. Existing/historical carried Workspace artifacts remain preservation inputs and are not reclassified as new candidates by package carriage.'
25
+ });
26
+ }
@@ -7,6 +7,11 @@ import { qualifySecureTransportV1Envelope } from '../../../transport/secureTrans
7
7
  export const TRANSPORT_ENVELOPE_V1_SCHEMA_ID = 'tiinex.transport.envelope.v1';
8
8
  export const TRANSPORT_ENVELOPE_V1_ROLE = 'password-sealed-workspace-transport-envelope';
9
9
 
10
+ // Bounded exception: the Transport Envelope schema is intentionally not assigned a
11
+ // published immutable canonical locator. Keep the plain schema id until such authority
12
+ // exists; do not substitute a mutable branch/latest URL. Secure-transport regressions
13
+ // lock this fail-closed representation choice.
14
+
10
15
  export function renderTransportEnvelopeV1(input = {}) {
11
16
  const envelope = input.envelope || {};
12
17
  const q = qualifySecureTransportV1Envelope(envelope);
@@ -54,3 +54,4 @@ export * from './handoff/transportEnvelopeV1.js';
54
54
  export * from './handoff/recipientV2.packageV1.js';
55
55
 
56
56
  export * from './comparison/sourceFrontierComparison.js';
57
+ export * from './comparison/sourceFrontierReconciliationProof.js';
@@ -39,10 +39,18 @@ import { projectPortableOperatingOverview } from './overview/operatingOverview.j
39
39
  import { projectPortableGroundingReadiness } from './grounding/grounding.readiness.js';
40
40
  import { createPortablePackageOperationEntries } from './operation.catalog.package.js';
41
41
  import { compareOrReconcilePortableSourceFrontiers } from './comparison/sourceFrontierComparison.js';
42
+ import { provePortableSourceReconciliation } from './comparison/sourceFrontierReconciliationProof.js';
42
43
 
43
44
  export const PORTABLE_OPERATION_CATALOG_SCHEMA_ID = 'tiinex.portable.operation.catalog.v1';
44
45
 
45
46
  export const portableOperationCatalog = Object.freeze({
47
+ 'prove-source-reconciliation': operation({
48
+ name: 'prove-source-reconciliation',
49
+ description: 'Fail closed unless qualified base/incoming/current source plus explicit conflict/deletion dispositions are preserved by one exact candidate reconciled frontier suitable for manufacture requalification.',
50
+ safety: 'read-only-mechanical-proof',
51
+ inputSchema: 'tiinex.portable.source-frontier-reconciliation-proof.request.v1',
52
+ handler: (input = {}) => wrapPortableResult('prove-source-reconciliation', provePortableSourceReconciliation(input))
53
+ }),
46
54
  'compare-source-frontiers': operation({
47
55
  name: 'compare-source-frontiers',
48
56
  description: 'Compare normalized exact Workspace source frontiers two-way or reconcile base/incoming/current three-way without merge, semantic inference, remote acquisition, or source mutation.',
@@ -0,0 +1,108 @@
1
+ export const PORTABLE_SOURCE_ELIGIBILITY_SCHEMA_ID = 'tiinex.portable.source-eligibility.v1';
2
+
3
+ export const PORTABLE_GENERATED_SOURCE_EXCLUDED_DIRECTORY_NAMES = Object.freeze(['__pycache__']);
4
+ export const PORTABLE_GENERATED_SOURCE_EXCLUDED_FILE_SUFFIXES = Object.freeze(['.pyc', '.pyo']);
5
+ export const DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES = Object.freeze([
6
+ '.git',
7
+ '.tiinex',
8
+ 'node_modules',
9
+ '.site-publish',
10
+ '.release',
11
+ '.outgoing-handoff-packages',
12
+ ...PORTABLE_GENERATED_SOURCE_EXCLUDED_DIRECTORY_NAMES
13
+ ]);
14
+ export const DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS = Object.freeze(['.vscode/link']);
15
+
16
+ /**
17
+ * Host-neutral source eligibility for one Workspace-relative path.
18
+ *
19
+ * This is a mechanical durable-source boundary only. Exclusion means a path is not
20
+ * part of source-frontier identity; it does not grant deletion, merge, acceptance,
21
+ * or semantic authority over bytes carried as historical transport evidence.
22
+ */
23
+ export function qualifyPortableSourcePath(pathInput = '', options = {}) {
24
+ const entryKind = String(options.entryKind || 'file').trim().toLowerCase() === 'directory' ? 'directory' : 'file';
25
+ const path = normalizePortableSourcePath(pathInput);
26
+ const policy = portableSourceEligibilityPolicy(options);
27
+ if (!path) return freezeQualification(path, true, 'empty-or-unqualified-path', '', entryKind, policy);
28
+
29
+ const segments = path.split('/').filter(Boolean);
30
+ const directorySegments = entryKind === 'directory' ? segments : segments.slice(0, -1);
31
+ for (const name of directorySegments) {
32
+ if (!policy.excludedDirectories.includes(name)) continue;
33
+ return freezeQualification(path, false, 'excluded-directory', name, entryKind, policy);
34
+ }
35
+
36
+ for (const relativePath of policy.excludedRelativePaths) {
37
+ if (path === relativePath || path.startsWith(`${relativePath}/`)) {
38
+ return freezeQualification(path, false, 'excluded-relative-path', relativePath, entryKind, policy);
39
+ }
40
+ }
41
+
42
+ if (entryKind === 'file') {
43
+ const basename = String(segments.at(-1) || '').toLowerCase();
44
+ for (const suffix of policy.excludedFileSuffixes) {
45
+ if (!basename.endsWith(suffix)) continue;
46
+ return freezeQualification(path, false, 'compiled-python-cache', suffix, entryKind, policy);
47
+ }
48
+ }
49
+
50
+ return freezeQualification(path, true, 'eligible-source', '', entryKind, policy);
51
+ }
52
+
53
+ export function isPortableSourceEligiblePath(pathInput = '', options = {}) {
54
+ return qualifyPortableSourcePath(pathInput, options).eligible;
55
+ }
56
+
57
+ export function portableSourceEligibilityPolicy(options = {}) {
58
+ const requestedDirectories = options.excludeDirectories == null
59
+ ? DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES
60
+ : options.excludeDirectories;
61
+ const requestedRelativePaths = options.excludeRelativePaths == null
62
+ ? DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS
63
+ : options.excludeRelativePaths;
64
+
65
+ const excludedDirectories = uniqueSorted([
66
+ ...PORTABLE_GENERATED_SOURCE_EXCLUDED_DIRECTORY_NAMES,
67
+ ...iterableStrings(requestedDirectories)
68
+ ]);
69
+ const excludedRelativePaths = uniqueSorted(iterableStrings(requestedRelativePaths).map(normalizePortableSourcePath).filter(Boolean));
70
+ const excludedFileSuffixes = uniqueSorted(PORTABLE_GENERATED_SOURCE_EXCLUDED_FILE_SUFFIXES.map((value) => String(value).toLowerCase()));
71
+
72
+ return Object.freeze({
73
+ schema: PORTABLE_SOURCE_ELIGIBILITY_SCHEMA_ID,
74
+ excludedDirectories: Object.freeze(excludedDirectories),
75
+ excludedRelativePaths: Object.freeze(excludedRelativePaths),
76
+ excludedFileSuffixes: Object.freeze(excludedFileSuffixes),
77
+ boundary: 'Mechanical durable-source eligibility only. Historical transport bytes remain evidence; exclusion grants no semantic deletion or merge authority.'
78
+ });
79
+ }
80
+
81
+ export function normalizePortableSourcePath(value = '') {
82
+ return String(value || '').trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+/, '').replace(/\/+$/, '');
83
+ }
84
+
85
+ function freezeQualification(path, eligible, reason, matchedRule, entryKind, policy) {
86
+ return Object.freeze({
87
+ schema: PORTABLE_SOURCE_ELIGIBILITY_SCHEMA_ID,
88
+ state: eligible ? 'eligible' : 'excluded',
89
+ eligible,
90
+ path,
91
+ entryKind,
92
+ reason,
93
+ matchedRule,
94
+ policy,
95
+ boundary: 'Path classification is mechanical source eligibility only; it is not a semantic disposition.'
96
+ });
97
+ }
98
+
99
+ function iterableStrings(value) {
100
+ if (Array.isArray(value)) return value.map(String);
101
+ if (value instanceof Set) return [...value].map(String);
102
+ if (value == null) return [];
103
+ return [String(value)];
104
+ }
105
+
106
+ function uniqueSorted(values) {
107
+ return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))].sort();
108
+ }
@@ -1,10 +1,11 @@
1
1
  import { parseArtifactMarkdown } from '../artifacts/artifact.parse.js';
2
2
  import { normalizeArtifact } from '../artifacts/artifact.normalize.js';
3
3
  import { resolveSchemaModule } from '../schemas/resolver.js';
4
+ import { schemaRegistry } from '../schemas/registry.js';
4
5
  import { rootValidate, rootFallbackFinding } from '../schemas/tiinex.root.v1.validate.js';
5
6
  import { validateIntegrity } from '../integrity/integrity.validate.js';
6
7
  import { validatePortableContractInstance } from '../tooling/portable/schema/contract.validate.js';
7
- import { qualifySchemaReferenceValue, schemaReferenceAuthorityFromBinding } from '../schemas/schema.reference.js';
8
+ import { qualifySchemaReferenceValue, qualifiedExactSchemaReferenceTarget, schemaReferenceAuthorityFromBinding } from '../schemas/schema.reference.js';
8
9
  import { normalizeFindings, normalizeFinding } from './findings.js';
9
10
 
10
11
  export const ARTIFACT_VALIDATION_PIPELINE_ID = 'tiinex.artifact.validation.pipeline.v1';
@@ -18,7 +19,7 @@ export function validateArtifact(input = {}, options = {}) {
18
19
  const schemaValidationAuthority = input.schemaValidationAuthority || null;
19
20
  const machineContract = runMachineContractValidation({ markdown: markdown || parsed?.markdown || '', schemaId, resolution, validationContractOverride: input.validationContractOverride || null, schemaValidationAuthority });
20
21
  const contractFindings = normalizeFindings(machineContract.findings, { schemaId: machineContract.schemaId || schemaId, qualification: 'machine-contract' });
21
- const schemaReferenceFindings = normalizeFindings(validateDeclaredSchemaReferences(parsed, input.schemaReferenceAuthorities || null), { qualification: 'schema-reference' });
22
+ const schemaReferenceFindings = normalizeFindings(validateDeclaredSchemaReferences(parsed, input.schemaReferenceAuthorities || null, { context: input.schemaReferenceContext || 'historical' }), { qualification: 'schema-reference' });
22
23
  const integrityFindings = normalizeFindings(validateIntegrity(parsed, options.integrity), { schemaId: 'tiinex.root.v1', qualification: 'integrity' });
23
24
  const schemaAuthorityFindings = normalizeFindings(schemaValidationAuthorityFindings(schemaValidationAuthority, schemaId), { schemaId, qualification: 'schema-validation-authority' });
24
25
  const childValidation = runExactSchemaValidator({ parsed, schemaId, resolution, schemaValidationAuthority });
@@ -39,7 +40,7 @@ export function validateArtifact(input = {}, options = {}) {
39
40
  });
40
41
  }
41
42
 
42
- function validateDeclaredSchemaReferences(parsed = {}, contextualAuthorities = null) {
43
+ function validateDeclaredSchemaReferences(parsed = {}, contextualAuthorities = null, options = {}) {
43
44
  const references = [
44
45
  { role: 'Envelope Schema', value: parsed?.envelope?.envelopeSchema?.raw || '', schemaId: parsed?.envelope?.envelopeSchema?.id || '' },
45
46
  { role: 'Current Schema', value: parsed?.envelope?.current?.schema?.raw || '', schemaId: parsed?.envelope?.current?.schema?.id || '' }
@@ -59,21 +60,76 @@ function validateDeclaredSchemaReferences(parsed = {}, contextualAuthorities = n
59
60
  const contextualAuthority = contextualSchemaReferenceAuthority(contextualAuthorities, reference.role, reference.schemaId);
60
61
  const authority = contextualAuthority || registeredAuthority;
61
62
  const qualification = qualifySchemaReferenceValue(reference.value, authority);
63
+ const context = String(options?.context || 'historical').trim() === 'candidate' ? 'candidate' : 'historical';
64
+ const exactTarget = qualifiedExactSchemaReferenceTarget(authority);
65
+ if (qualification.schemaIdState === 'qualified' && qualification.observed?.form === 'plain-schema-id' && exactTarget) {
66
+ const prospective = context === 'candidate';
67
+ findings.push({
68
+ severity: prospective ? 'error' : 'warning',
69
+ code: 'schema.reference.exact-target-omitted',
70
+ message: prospective
71
+ ? `${reference.role}: qualified immutable exact schema-reference authority exists for ${reference.schemaId}; a new candidate must use Markdown Link form targeting ${exactTarget} before sealing, staging, acceptance, or manufacture.`
72
+ : `${reference.role}: a qualified immutable exact schema-reference target is currently available for ${reference.schemaId}, but this existing artifact uses only the schema id. Preserve the historical bytes; treat this as reference-quality debt rather than an in-place rewrite instruction.`,
73
+ source: 'tiinex.schema.reference.validation.v1',
74
+ state: prospective ? 'blocking-prospective-reference-omission' : 'historical-reference-debt',
75
+ params: { field: reference.role, schemaId: reference.schemaId, exactTarget, context }
76
+ });
77
+ continue;
78
+ }
62
79
  if (qualification.state === 'qualified') continue;
63
80
  if (qualification.schemaIdState !== 'qualified') {
64
- findings.push({ severity: 'error', code: 'schema.reference.identity-contradiction', message: `${reference.role}: Declared schema identifier contradicts current semantic schema identity authority. ${qualification.findings.join(' ')}`, source: 'tiinex.schema.reference.validation.v1', params: { field: reference.role } });
81
+ findings.push({ severity: 'error', code: 'schema.reference.identity-contradiction', message: `${reference.role}: Declared schema identifier contradicts current semantic schema identity authority. ${qualification.findings.join(' ')}`, source: 'tiinex.schema.reference.validation.v1', params: { field: reference.role, schemaId: reference.schemaId, context } });
65
82
  continue;
66
83
  }
67
84
  if (qualification.observed?.form === 'markdown-link' && qualification.targetState === 'unqualified') {
68
- findings.push({ severity: 'info', code: 'schema.reference.locator.unresolved', message: `${reference.role}: Declared schema representation locator is preserved but is not resolved by current exact material authority. Locator resolution is separate from semantic schema identity.`, source: 'tiinex.schema.reference.validation.v1' });
85
+ const observedTarget = String(qualification.observed?.target || '');
86
+ const targetIdentity = qualifiedRegisteredSchemaIdentitiesForTarget(observedTarget);
87
+ const contradictory = targetIdentity.length > 0 && !targetIdentity.includes(reference.schemaId);
88
+ if (contradictory) {
89
+ findings.push({
90
+ severity: 'error',
91
+ code: 'schema.reference.material-identity-contradiction',
92
+ message: `${reference.role}: the declared schema representation locator is positively qualified as ${targetIdentity.join(', ')}, not ${reference.schemaId}; resolved material identity contradictions are blocking even for preserved historical artifacts.`,
93
+ source: 'tiinex.schema.reference.validation.v1',
94
+ state: 'blocking-resolved-material-identity-contradiction',
95
+ params: { field: reference.role, schemaId: reference.schemaId, observedTarget, resolvedSchemaIds: targetIdentity, context }
96
+ });
97
+ continue;
98
+ }
99
+ const prospective = context === 'candidate';
100
+ findings.push({
101
+ severity: prospective ? 'error' : 'info',
102
+ code: prospective ? 'schema.reference.target-unqualified' : 'schema.reference.locator.unresolved',
103
+ message: prospective
104
+ ? `${reference.role}: the declared schema representation locator is not qualified for the exact governing schema material and cannot be carried into a new candidate unchanged.`
105
+ : `${reference.role}: Declared schema representation locator is preserved but is not resolved by current exact material authority. Locator resolution is separate from semantic schema identity.`,
106
+ source: 'tiinex.schema.reference.validation.v1',
107
+ state: prospective ? 'blocking-prospective-reference-contradiction' : 'historical-locator-unresolved',
108
+ params: { field: reference.role, schemaId: reference.schemaId, observedTarget, exactTarget, context }
109
+ });
69
110
  continue;
70
111
  }
71
- findings.push({ severity: 'error', code: 'schema.reference.unqualified', message: `${reference.role}: ${qualification.findings.join(' ')}`, source: 'tiinex.schema.reference.validation.v1', params: { field: reference.role } });
112
+ findings.push({ severity: 'error', code: 'schema.reference.unqualified', message: `${reference.role}: ${qualification.findings.join(' ')}`, source: 'tiinex.schema.reference.validation.v1', params: { field: reference.role, schemaId: reference.schemaId, context } });
72
113
  }
73
114
  return findings;
74
115
  }
75
116
 
76
117
 
118
+ function qualifiedRegisteredSchemaIdentitiesForTarget(target = '') {
119
+ const observedTarget = String(target || '').trim();
120
+ if (!observedTarget) return Object.freeze([]);
121
+ const schemaIds = [];
122
+ for (const module of schemaRegistry.modules || []) {
123
+ const schemaId = String(module?.id || '').trim();
124
+ if (!schemaId) continue;
125
+ const sourceQualification = typeof module?.schemaSource?.qualify === 'function' ? module.schemaSource.qualify() : null;
126
+ const authority = schemaReferenceAuthorityFromBinding(schemaId, module?.binding || {}, sourceQualification?.authority || null, sourceQualification);
127
+ if (qualifiedExactSchemaReferenceTarget(authority) === observedTarget) schemaIds.push(schemaId);
128
+ }
129
+ return Object.freeze([...new Set(schemaIds)]);
130
+ }
131
+
132
+
77
133
  function contextualSchemaReferenceAuthority(value = null, role = '', schemaId = '') {
78
134
  if (!value || typeof value !== 'object') return null;
79
135
  const key = role === 'Envelope Schema' ? 'envelope' : role === 'Current Schema' ? 'current' : role === 'Parent Schema' ? 'parent' : '';