@tiinex/core 0.31.0 → 0.33.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.
- package/package.json +5 -5
- package/src/schemas/creation.contracts.js +92 -10
- package/src/schemas/creation.renderer.js +10 -3
- package/src/tooling/portable/adapters/cli/cli.editor-assistance.js +5 -2
- package/src/tooling/portable/adapters/cli/cli.handoff-sibling-allocation.js +20 -1
- package/src/tooling/portable/draft/draft.exact.js +1 -1
- package/src/tooling/portable/editor/authoring.parent.js +14 -4
- package/src/tooling/portable/editor/editor.assistance.js +272 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiinex/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.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,
|
|
@@ -173,12 +173,12 @@
|
|
|
173
173
|
"type": "git",
|
|
174
174
|
"url": "git+https://github.com/Tiinex/core.git"
|
|
175
175
|
},
|
|
176
|
-
"gitHead": "
|
|
176
|
+
"gitHead": "a317bd83e450d04e6e9b0a5229944d958b61eb91",
|
|
177
177
|
"tiinexRelease": {
|
|
178
178
|
"policy": "tiinex.master-npm-release.v1",
|
|
179
|
-
"sourceCommit": "
|
|
180
|
-
"sourceTree": "
|
|
179
|
+
"sourceCommit": "a317bd83e450d04e6e9b0a5229944d958b61eb91",
|
|
180
|
+
"sourceTree": "7f7ff3bb4a7926eb901476d777630c3d536ebed2",
|
|
181
181
|
"repository": "Tiinex/core",
|
|
182
|
-
"previousVersion": "0.
|
|
182
|
+
"previousVersion": "0.32.0"
|
|
183
183
|
}
|
|
184
184
|
}
|
|
@@ -10,6 +10,7 @@ import { projectPortableValidationContractWithQualifiedLocalRoot } from '../tool
|
|
|
10
10
|
import { qualifyRootCreationRepresentation, qualifyContinuationCreationRepresentation } from './creation.representation.js';
|
|
11
11
|
import { qualifyCreationSchemaReferences, schemaReferenceAuthoritiesForCreation } from './creation.schemaReferences.js';
|
|
12
12
|
import { qualifySchemaReferenceValue } from './schema.reference.js';
|
|
13
|
+
import { qualifiedCreationAuthorityFromSchemaSource } from './schema.source.js';
|
|
13
14
|
import { C14N_V2_METHOD_ID, integrityMethodReferenceAuthorityForCreation } from '../integrity/integrity.methodReference.js';
|
|
14
15
|
|
|
15
16
|
export const ARTIFACT_CREATION_CONTRACT_SCHEMA_ID = 'tiinex.artifact.creation.contract.v1';
|
|
@@ -39,16 +40,7 @@ export function buildArtifactCreationContract(input = {}, options = {}) {
|
|
|
39
40
|
const transitionType = String(input.transitionType || options.transitionType || 'create-artifact').trim();
|
|
40
41
|
const creationCapability = qualifyArtifactCreationCapability(module, transitionType);
|
|
41
42
|
const creationAuthority = creationCapability.authority?.compiledContract?.creation || {};
|
|
42
|
-
const creation =
|
|
43
|
-
requiredInputs: list(creationAuthority.requiredInputs),
|
|
44
|
-
optionalInputs: list(creationAuthority.optionalInputs),
|
|
45
|
-
requiredSections: list(creationAuthority.requiredSections),
|
|
46
|
-
representationSections: list(creationAuthority.representationSections),
|
|
47
|
-
toolingConfigurationFields: list(creationAuthority.toolingConfigurationFields),
|
|
48
|
-
inputBindings: list(creationAuthority.inputBindings),
|
|
49
|
-
supplementalRequiredFields: list(creationAuthority.supplementalRequiredFields),
|
|
50
|
-
requiredShape: list(creationAuthority.requiredShape)
|
|
51
|
-
});
|
|
43
|
+
const creation = composeCreationAuthority(module, creationCapability.authority, creationAuthority);
|
|
52
44
|
const renderer = creationCapability.implementation?.state === 'implemented'
|
|
53
45
|
? { status: CapabilityStatus.implemented, ...(creationCapability.implementation.renderer || {}) }
|
|
54
46
|
: { status: CapabilityStatus.unavailable, id: '', scope: transitionType };
|
|
@@ -203,6 +195,96 @@ export function validateArtifactCreationResult(draft = {}, parentRecord = {}, op
|
|
|
203
195
|
|
|
204
196
|
|
|
205
197
|
|
|
198
|
+
|
|
199
|
+
function composeCreationAuthority(module = null, authority = {}, localCreation = {}) {
|
|
200
|
+
const validationContract = authority?.compiledContract?.validationContract || null;
|
|
201
|
+
const lineage = Array.isArray(validationContract?.lineage) && validationContract.lineage.length
|
|
202
|
+
? validationContract.lineage
|
|
203
|
+
: [String(module?.id || authority?.schemaId || '')].filter(Boolean);
|
|
204
|
+
const creationAuthorities = [];
|
|
205
|
+
for (const schemaId of lineage) {
|
|
206
|
+
const lineageModule = schemaRegistry.byId?.get(schemaId) || null;
|
|
207
|
+
if (!lineageModule) continue;
|
|
208
|
+
const qualified = qualifiedCreationAuthorityFromSchemaSource(lineageModule);
|
|
209
|
+
if (qualified.state !== 'qualified' || !qualified.compiledContract?.creation) continue;
|
|
210
|
+
creationAuthorities.push({ schemaId, creation: qualified.compiledContract.creation });
|
|
211
|
+
}
|
|
212
|
+
if (!creationAuthorities.length) creationAuthorities.push({ schemaId: String(module?.id || ''), creation: localCreation });
|
|
213
|
+
|
|
214
|
+
const requiredHeadingOrder = [...(validationContract?.validation?.requiredHeadings || [])]
|
|
215
|
+
.filter((item) => Number(item?.level || 0) === 2 && String(item?.title || '').trim())
|
|
216
|
+
.map((item) => String(item.title).trim());
|
|
217
|
+
const requiredHeadingSet = new Set(requiredHeadingOrder);
|
|
218
|
+
const bindingByInput = new Map();
|
|
219
|
+
const requiredInputSet = new Set();
|
|
220
|
+
const optionalInputSet = new Set();
|
|
221
|
+
const toolingFields = [];
|
|
222
|
+
const supplementalByKey = new Map();
|
|
223
|
+
const shapeByKey = new Map();
|
|
224
|
+
|
|
225
|
+
for (const entry of creationAuthorities) {
|
|
226
|
+
const creation = entry.creation || {};
|
|
227
|
+
for (const value of creation.requiredInputs || []) requiredInputSet.add(String(value || '').trim());
|
|
228
|
+
for (const value of creation.optionalInputs || []) optionalInputSet.add(String(value || '').trim());
|
|
229
|
+
for (const value of creation.toolingConfigurationFields || []) pushUnique(toolingFields, String(value || '').trim());
|
|
230
|
+
for (const item of creation.inputBindings || []) {
|
|
231
|
+
const input = String(item?.input || '').trim();
|
|
232
|
+
if (!input) continue;
|
|
233
|
+
const section = String(item?.section || '').trim();
|
|
234
|
+
if (section && requiredHeadingSet.size && !requiredHeadingSet.has(section)) continue;
|
|
235
|
+
bindingByInput.set(input, item);
|
|
236
|
+
}
|
|
237
|
+
for (const item of creation.supplementalRequiredFields || []) {
|
|
238
|
+
const section = String(item?.section || '').trim();
|
|
239
|
+
if (section && requiredHeadingSet.size && !requiredHeadingSet.has(section)) continue;
|
|
240
|
+
const key = `${section}\u0000${String(item?.field || '')}`;
|
|
241
|
+
supplementalByKey.set(key, item);
|
|
242
|
+
}
|
|
243
|
+
for (const item of creation.requiredShape || []) {
|
|
244
|
+
const primitive = item?.primitive || {};
|
|
245
|
+
const section = String(primitive?.section || '').trim();
|
|
246
|
+
if (section && requiredHeadingSet.size && !requiredHeadingSet.has(section)) continue;
|
|
247
|
+
const key = `${String(primitive?.kind || '')}\u0000${String(primitive?.input || '')}\u0000${section}\u0000${String(primitive?.position || '')}`;
|
|
248
|
+
shapeByKey.set(key, item);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const bindings = [];
|
|
253
|
+
for (const item of bindingByInput.values()) if (!String(item?.section || '').trim()) bindings.push(item);
|
|
254
|
+
for (const section of requiredHeadingOrder) {
|
|
255
|
+
for (const item of bindingByInput.values()) if (String(item?.section || '').trim() === section && !bindings.includes(item)) bindings.push(item);
|
|
256
|
+
}
|
|
257
|
+
for (const item of bindingByInput.values()) if (!bindings.includes(item)) bindings.push(item);
|
|
258
|
+
|
|
259
|
+
const boundInputs = new Set(bindings.map((item) => String(item?.input || '').trim()).filter(Boolean));
|
|
260
|
+
const requiredInputs = [...requiredInputSet].filter((input) => input && boundInputs.has(input));
|
|
261
|
+
const optionalInputs = [...optionalInputSet].filter((input) => input && boundInputs.has(input) && !requiredInputSet.has(input));
|
|
262
|
+
const requiredSections = requiredHeadingOrder.length
|
|
263
|
+
? requiredHeadingOrder.filter((section) => bindings.some((item) => String(item?.section || '').trim() === section))
|
|
264
|
+
: uniqueStrings(creationAuthorities.flatMap((entry) => entry.creation?.requiredSections || []));
|
|
265
|
+
const representationSections = requiredSections.length
|
|
266
|
+
? requiredSections
|
|
267
|
+
: uniqueStrings(bindings.map((item) => String(item?.section || '').trim()).filter(Boolean));
|
|
268
|
+
|
|
269
|
+
return Object.freeze({
|
|
270
|
+
requiredInputs: Object.freeze(requiredInputs),
|
|
271
|
+
optionalInputs: Object.freeze(optionalInputs),
|
|
272
|
+
requiredSections: Object.freeze(requiredSections),
|
|
273
|
+
representationSections: Object.freeze(representationSections),
|
|
274
|
+
toolingConfigurationFields: Object.freeze(toolingFields),
|
|
275
|
+
inputBindings: Object.freeze(bindings),
|
|
276
|
+
supplementalRequiredFields: Object.freeze([...supplementalByKey.values()]),
|
|
277
|
+
requiredShape: Object.freeze([...shapeByKey.values()])
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function uniqueStrings(values = []) {
|
|
282
|
+
const out = [];
|
|
283
|
+
for (const value of values || []) pushUnique(out, String(value || '').trim());
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
function pushUnique(values, value) { if (value && !values.includes(value)) values.push(value); }
|
|
287
|
+
|
|
206
288
|
function validateParentTargetIntegrity(parsed = {}, parentRecord = {}, expectedTarget = '') {
|
|
207
289
|
const entries = (parsed?.integrity?.entries || []).filter((entry) => entry?.method === C14N_V2_METHOD_ID && String(entry?.towards || '') !== 'self');
|
|
208
290
|
if (entries.length !== 1) return [];
|
|
@@ -38,7 +38,7 @@ export function renderArtifactCreationDraftMarkdown(contract = {}, input = {}) {
|
|
|
38
38
|
const lines = [
|
|
39
39
|
'# Continuity Context', '',
|
|
40
40
|
`- Envelope Schema: ${envelopeSchemaReference}`,
|
|
41
|
-
...(parent ? renderParent(parent) : []),
|
|
41
|
+
...(parent ? renderParent(parent, { preserveUnqualifiedSchemaReference: input.preserveUnqualifiedParentSchemaReference === true }) : []),
|
|
42
42
|
'- Current',
|
|
43
43
|
` - Current Schema: ${currentSchemaReference}`,
|
|
44
44
|
` - Created At: ${createdAt}`,
|
|
@@ -69,10 +69,10 @@ export const genericArtifactCreationImplementation = Object.freeze({
|
|
|
69
69
|
execute: renderArtifactCreationDraftMarkdown
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
function renderParent(parent) {
|
|
72
|
+
function renderParent(parent, options = {}) {
|
|
73
73
|
return [
|
|
74
74
|
'- Parent',
|
|
75
|
-
` - Parent Schema: ${
|
|
75
|
+
` - Parent Schema: ${renderParentSchemaReference(parent.schemaReferenceAuthority, options.preserveUnqualifiedSchemaReference === true)}`,
|
|
76
76
|
...(parent.createdAt ? [` - Created At: ${parent.createdAt}`] : []),
|
|
77
77
|
` - Trace: [${parent.traceLabel}](${parent.traceReference})`,
|
|
78
78
|
' - Origin:',
|
|
@@ -81,6 +81,13 @@ function renderParent(parent) {
|
|
|
81
81
|
];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
function renderParentSchemaReference(authority = {}, preserveUnqualified = false) {
|
|
85
|
+
const schemaId = String(authority?.schemaId || '').trim();
|
|
86
|
+
const preferredTarget = String(authority?.preferredTarget || authority?.target || '').trim();
|
|
87
|
+
if (preserveUnqualified && schemaId && preferredTarget) return `[${schemaId}](${preferredTarget})`;
|
|
88
|
+
return renderSchemaReference(authority);
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
function contractDrivenBodyMarkdown(contract = {}, { title = '', values = {} } = {}) {
|
|
85
92
|
const creation = contract?.creation || {};
|
|
86
93
|
const bindings = Array.isArray(creation.inputBindings) ? creation.inputBindings : [];
|
|
@@ -2,10 +2,13 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
|
|
3
3
|
export async function prepareEditorAssistanceCliInput(material = {}, flags = {}) {
|
|
4
4
|
const focusPath = norm(flags.focus || flags.path || '');
|
|
5
|
-
|
|
5
|
+
const referenceResolutions = flags['reference-resolutions']
|
|
6
|
+
? JSON.parse(await readFile(String(flags['reference-resolutions']), 'utf8'))
|
|
7
|
+
: [];
|
|
8
|
+
if (!focusPath || !flags.overlay) return { input: { ...material, focusPath, referenceResolutions }, options: {} };
|
|
6
9
|
const overlayMarkdown = await readFile(String(flags.overlay), 'utf8');
|
|
7
10
|
const files = (material.files || []).map((file) => norm(file.path || '') === focusPath ? { ...file, content: overlayMarkdown } : file);
|
|
8
|
-
const input = { ...material, files, focusPath };
|
|
11
|
+
const input = { ...material, files, focusPath, referenceResolutions };
|
|
9
12
|
if (Array.isArray(material.records)) input.records = material.records.map((record) => norm(record.path || record.id || '') === focusPath ? { ...record, markdown: overlayMarkdown } : record);
|
|
10
13
|
return { input, options: {} };
|
|
11
14
|
}
|
|
@@ -30,7 +30,26 @@ export function deriveHandoffSiblingAllocation({ parentInspection = null, select
|
|
|
30
30
|
workspaceId: String(route.workspaceId || '').trim(),
|
|
31
31
|
workspaceRelativeHandoffPath: String(route.workspaceRelativeHandoffPath || '').trim()
|
|
32
32
|
}));
|
|
33
|
-
if (!routes.length)
|
|
33
|
+
if (!routes.length) {
|
|
34
|
+
const siblingIndex = 1;
|
|
35
|
+
if (explicit && explicit !== siblingIndex) return blocked('explicit-sibling-index-conflicts-with-qualified-pointerless-topology', { expectedSiblingIndex: siblingIndex });
|
|
36
|
+
return freeze({
|
|
37
|
+
state: 'qualified',
|
|
38
|
+
siblingIndex,
|
|
39
|
+
childDimension: parentDimension ? `${String(parentDimension).trim()}-${siblingIndex}` : '',
|
|
40
|
+
allocationMode: 'qualified-parent-pointerless-default',
|
|
41
|
+
explicitOverride: explicit ? 'matched-derived-value' : 'not-supplied',
|
|
42
|
+
reasonCode: '',
|
|
43
|
+
provenance: {
|
|
44
|
+
...provenanceBase({ parentPackagePath, parentPackageSha256, parentDimension, explicitSiblingIndex: explicit }),
|
|
45
|
+
basis: 'qualified-parent-pointerless-default',
|
|
46
|
+
routeOrdinal: siblingIndex,
|
|
47
|
+
qualifiedRouteCount: 0,
|
|
48
|
+
pointerOrder: []
|
|
49
|
+
},
|
|
50
|
+
boundary: allocationBoundary()
|
|
51
|
+
});
|
|
52
|
+
}
|
|
34
53
|
|
|
35
54
|
const qualified = [];
|
|
36
55
|
const seenDimensions = new Set();
|
|
@@ -78,7 +78,7 @@ export function renderQualifiedPortableExactDraft({ contract = {}, schemaId = ''
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export function renderPortableLocalContinuityDraft({ contract = {}, schemaId = '', transitionType = 'continue-from-record', parentSnapshot = {}, rendererInput = {} } = {}) {
|
|
81
|
-
const markdown = renderArtifactCreationCandidateMarkdown(contract, { ...rendererInput, parentRecord: parentSnapshot, currentSchemaId: schemaId });
|
|
81
|
+
const markdown = renderArtifactCreationCandidateMarkdown(contract, { ...rendererInput, parentRecord: parentSnapshot, currentSchemaId: schemaId, preserveUnqualifiedParentSchemaReference: true });
|
|
82
82
|
if (!markdown) return Object.freeze({ state: 'unqualified', reason: 'local-continuity-renderer-empty', markdown: '', validation: null, parentRepresentation: null });
|
|
83
83
|
const parentRepresentation = qualifyPortableRenderedParentRepresentation(markdown, parentSnapshot, transitionType, rendererInput.childPath || '', { allowUnpublishedLocalParent: true });
|
|
84
84
|
if (parentRepresentation.state !== 'qualified') return Object.freeze({ state: 'unqualified', reason: parentRepresentation.reason || 'local-continuity-parent-representation-mismatch', markdown: '', validation: null, parentRepresentation });
|
|
@@ -2,6 +2,7 @@ import { parseArtifactMarkdown } from '../../../artifacts/artifact.parse.js';
|
|
|
2
2
|
import { auditPortableRecord } from '../audit/audit.capability.js';
|
|
3
3
|
import { classifyParentRecoveryReference } from '../../../lineage/parentRecoveryReference.js';
|
|
4
4
|
import { buildArtifactCreationContract } from '../../../schemas/creation.contracts.js';
|
|
5
|
+
import { canonicalC14nV2SelfState } from '../../../integrity/integrity.c14nV2.js';
|
|
5
6
|
|
|
6
7
|
export const PORTABLE_AUTHORING_PARENT_SCHEMA_ID = 'tiinex.portable.authoring-parent.v1';
|
|
7
8
|
|
|
@@ -12,8 +13,15 @@ export function projectPortableAuthoringParent(input = {}) {
|
|
|
12
13
|
let parsed;
|
|
13
14
|
try { parsed = parseArtifactMarkdown(record.markdown); }
|
|
14
15
|
catch { return freeze({ schema: PORTABLE_AUTHORING_PARENT_SCHEMA_ID, status: 'blocked', parentRecord: null, findings: [{ severity: 'error', code: 'portable.authoring-parent.parse-failed', message: 'Selected Parent bytes are not a readable Tiinex artifact.' }], operationBoundary: boundary() }); }
|
|
15
|
-
const audit = auditPortableRecord({ ...record, title: parsed.title, schemaId: parsed.envelope?.current?.schema?.id, currentSchemaId: parsed.envelope?.current?.schema?.id, parent: parsed.envelope?.parent });
|
|
16
|
-
|
|
16
|
+
const audit = auditPortableRecord({ ...record, title: parsed.title, schemaId: parsed.envelope?.current?.schema?.id, currentSchemaId: parsed.envelope?.current?.schema?.id, parent: parsed.envelope?.parent }, { requireExactSchemaAuthority: true });
|
|
17
|
+
const auditErrors = (audit.findings || []).filter((item) => item.severity === 'error');
|
|
18
|
+
const historicalParentRecoveryDebt = auditErrors.length > 0 && auditErrors.every((item) => String(item.code || '') === 'root.parent.recovery.workspace-qualified.malformed');
|
|
19
|
+
const selfIntegrity = canonicalC14nV2SelfState(record.markdown || '');
|
|
20
|
+
const directParentUsableWithHistoricalDebt = historicalParentRecoveryDebt
|
|
21
|
+
&& audit.qualification?.exact === true
|
|
22
|
+
&& audit.schemaValidationAuthority?.state === 'qualified'
|
|
23
|
+
&& selfIntegrity.state === 'verified';
|
|
24
|
+
if ((audit.status !== 'readable' && !directParentUsableWithHistoricalDebt) || audit.qualification?.exact !== true || (auditErrors.length && !directParentUsableWithHistoricalDebt)) return freeze({ schema: PORTABLE_AUTHORING_PARENT_SCHEMA_ID, status: 'blocked', parentRecord: null, findings: [{ severity: 'error', code: 'portable.authoring-parent.unqualified', message: 'Selected Parent must pass exact shared audit before it can be used for native authoring.' }], operationBoundary: boundary() });
|
|
17
25
|
const schemaId = String(parsed.envelope?.current?.schema?.id || audit.schemaId || '');
|
|
18
26
|
const schemaTarget = String(parsed.envelope?.current?.schema?.target || '');
|
|
19
27
|
const createdAt = String(parsed.envelope?.current?.createdAt || audit.artifact?.createdAt || '');
|
|
@@ -36,9 +44,11 @@ export function projectPortableAuthoringParent(input = {}) {
|
|
|
36
44
|
markdown: record.markdown, sourceMode: String(record.sourceMode || 'portable-node-local'),
|
|
37
45
|
schemaReferenceAuthority
|
|
38
46
|
},
|
|
39
|
-
findings: [],
|
|
47
|
+
findings: directParentUsableWithHistoricalDebt ? [{ severity: 'warning', code: 'portable.authoring-parent.historical-ancestor-recovery-debt', message: 'Selected Parent has historical malformed Workspace-qualified recovery references to its own ancestor. Direct child authoring is allowed from the Parent exact current bytes and verified self integrity; that ancestor debt is not repaired, inherited, or upgraded.' }] : [],
|
|
40
48
|
operationBoundary: boundary(),
|
|
41
|
-
boundary:
|
|
49
|
+
boundary: directParentUsableWithHistoricalDebt
|
|
50
|
+
? 'Projects exact supplied Parent current bytes for direct continuation while preserving unresolved historical ancestor-recovery debt on the Parent itself. The child does not inherit or repair that ancestor locator.'
|
|
51
|
+
: 'Projects exact supplied Parent bytes and declared current schema locator into shared draft-authoring input. A declared schema locator remains unresolved and is not upgraded to publication or canonical reference authority.'
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -5,6 +5,7 @@ import { integrityMethodReferenceAuthorityForCreation } from '../../../integrity
|
|
|
5
5
|
import { inspectPortableLineageIntegrity } from '../lineage/lineage.integrity.plan.js';
|
|
6
6
|
import { portableFinding } from '../findings.js';
|
|
7
7
|
import { qualifyTiinexRouteArtifact } from '../handoff/routeArtifactConformance.js';
|
|
8
|
+
import { classifyParentRecoveryReference } from '../../../lineage/parentRecoveryReference.js';
|
|
8
9
|
|
|
9
10
|
export const PORTABLE_EDITOR_ASSISTANCE_SCHEMA_ID = 'tiinex.portable.editor-assistance.v1';
|
|
10
11
|
|
|
@@ -13,7 +14,7 @@ export function projectPortableEditorAssistance(input = {}) {
|
|
|
13
14
|
const focusPath = norm(input.focusPath || input.focus || '');
|
|
14
15
|
const selectedRecords = focusPath ? records.filter((record) => norm(record.path || record.id || '') === focusPath) : records;
|
|
15
16
|
const lineageInspection = inspectPortableLineageIntegrity({ records });
|
|
16
|
-
const documents = selectedRecords.map((record) => projectDocument(record, records, lineageInspection));
|
|
17
|
+
const documents = selectedRecords.map((record) => projectDocument(record, records, lineageInspection, referenceResolutionsForRecord(input.referenceResolutions || [], record)));
|
|
17
18
|
const diagnostics = documents.flatMap((item) => item.diagnostics);
|
|
18
19
|
return freeze({
|
|
19
20
|
schema: PORTABLE_EDITOR_ASSISTANCE_SCHEMA_ID,
|
|
@@ -24,7 +25,7 @@ export function projectPortableEditorAssistance(input = {}) {
|
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
function projectDocument(record = {}, records = [], lineageInspection = null) {
|
|
28
|
+
function projectDocument(record = {}, records = [], lineageInspection = null, referenceResolutions = []) {
|
|
28
29
|
const audit = auditPortableRecord(record, { requireExactSchemaAuthority: true });
|
|
29
30
|
const markdown = String(record.markdown || '');
|
|
30
31
|
const recordPath = norm(record.path || record.id || '');
|
|
@@ -49,9 +50,37 @@ function projectDocument(record = {}, records = [], lineageInspection = null) {
|
|
|
49
50
|
diagnosticCodes: workspacePackagingRepair.diagnosticCodes,
|
|
50
51
|
boundary: 'Repairs only a tiinex.workspace.v1 artifact whose replacement independently qualifies through the same exact registered Workspace contract and c14n-v2 self-integrity requirements used by Handoff package manufacture. Existing resolver-capable Current Schema references are preserved; permalink refresh is a separate resolution operation and must not be inferred from integrity repair.'
|
|
51
52
|
}));
|
|
52
|
-
const
|
|
53
|
+
const referenceRepair = deterministicReferenceHygieneRepair(record, audit, markdown);
|
|
54
|
+
const referenceQualification = referenceRepair.state === 'ready'
|
|
55
|
+
? qualifyReplacementAgainstSharedGuardrails(record, records, referenceRepair.markdown, {
|
|
56
|
+
allowQualifiedExternalParentUnresolved: true,
|
|
57
|
+
allowExistingWarningCodes: (audit.findings || []).filter((item) => item.severity === 'warning').map((item) => String(item.code || ''))
|
|
58
|
+
})
|
|
59
|
+
: { state: 'unavailable' };
|
|
60
|
+
if (referenceRepair.state === 'ready' && referenceRepair.markdown !== markdown && referenceQualification.state === 'qualified') actions.push(freeze({
|
|
61
|
+
id: 'repair-qualified-references-and-self-integrity',
|
|
62
|
+
title: referenceRepair.parentReferenceChanged && referenceRepair.schemaReferenceChanged
|
|
63
|
+
? 'Repair Tiinex Parent/schema references and self integrity'
|
|
64
|
+
: referenceRepair.parentReferenceChanged
|
|
65
|
+
? 'Repair Tiinex Parent references and self integrity'
|
|
66
|
+
: 'Repair Tiinex schema reference and self integrity',
|
|
67
|
+
kind: 'replace-document',
|
|
68
|
+
qualification: 'deterministic-shared-core',
|
|
69
|
+
sourceSha256: sha256Hex(new TextEncoder().encode(markdown)),
|
|
70
|
+
replacementMarkdown: referenceRepair.markdown,
|
|
71
|
+
diagnosticCodes: referenceRepair.diagnosticCodes,
|
|
72
|
+
boundary: 'Repairs only deterministically malformed Workspace-qualified Parent recovery locators and/or a bare Current Schema id when exact qualified current-schema source authority exists; reseals self integrity and exposes the replacement only after shared audit and loaded-descendant guardrails re-qualify it. External Parent availability is not invented.'
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
const referenceResolution = projectReferenceResolutionAssistance(markdown, referenceResolutions);
|
|
76
|
+
diagnostics.push(...referenceResolution.diagnostics);
|
|
77
|
+
actions.push(...referenceResolution.actions);
|
|
78
|
+
|
|
79
|
+
const integrityRepair = deterministicIntegrityHygieneRepair(markdown, sharedFindings);
|
|
53
80
|
const repairQualification = integrityRepair.state === 'ready'
|
|
54
|
-
? qualifyReplacementAgainstSharedGuardrails(record, records, integrityRepair.markdown
|
|
81
|
+
? qualifyReplacementAgainstSharedGuardrails(record, records, integrityRepair.markdown, {
|
|
82
|
+
allowExistingWarningCodes: sharedFindings.filter((item) => item.severity === 'warning').map((item) => String(item.code || ''))
|
|
83
|
+
})
|
|
55
84
|
: { state: 'unavailable' };
|
|
56
85
|
if (integrityRepair.state === 'ready' && integrityRepair.markdown !== markdown && repairQualification.state === 'qualified') actions.push(freeze({
|
|
57
86
|
id: 'refresh-primary-self-integrity',
|
|
@@ -117,25 +146,223 @@ function deterministicWorkspacePackagingRepair(record = {}, audit = {}, markdown
|
|
|
117
146
|
return freeze({ state: 'ready', markdown: candidate, schemaReferenceChanged, diagnosticCodes });
|
|
118
147
|
}
|
|
119
148
|
|
|
120
|
-
function qualifyReplacementAgainstSharedGuardrails(record = {}, records = [], replacementMarkdown = '') {
|
|
149
|
+
function qualifyReplacementAgainstSharedGuardrails(record = {}, records = [], replacementMarkdown = '', options = {}) {
|
|
121
150
|
const focusPath = norm(record.path || record.id || '');
|
|
122
151
|
if (!focusPath || !replacementMarkdown) return freeze({ state: 'unavailable', reason: 'replacement-or-focus-unavailable' });
|
|
123
152
|
const replacedRecords = records.map((item) => norm(item.path || item.id || '') === focusPath ? { ...item, markdown: replacementMarkdown } : item);
|
|
124
153
|
const replacementRecord = replacedRecords.find((item) => norm(item.path || item.id || '') === focusPath);
|
|
125
154
|
if (!replacementRecord) return freeze({ state: 'unavailable', reason: 'focused-record-unavailable' });
|
|
126
155
|
const replacementAudit = auditPortableRecord(replacementRecord, { requireExactSchemaAuthority: true });
|
|
127
|
-
const
|
|
156
|
+
const allowedWarnings = new Set((options.allowExistingWarningCodes || []).map((item) => String(item || '')));
|
|
157
|
+
const auditBlockers = [...(replacementAudit.findings || [])].filter((item) => {
|
|
158
|
+
if (item.severity === 'error') return true;
|
|
159
|
+
if (item.severity !== 'warning') return false;
|
|
160
|
+
return !allowedWarnings.has(String(item.code || ''));
|
|
161
|
+
});
|
|
128
162
|
if (auditBlockers.length) return freeze({ state: 'blocked', reason: 'replacement-shared-audit-not-clean', blockerCodes: auditBlockers.map((item) => String(item.code || '')) });
|
|
129
163
|
|
|
130
164
|
const before = inspectPortableLineageIntegrity({ records });
|
|
131
165
|
const after = inspectPortableLineageIntegrity({ records: replacedRecords });
|
|
132
166
|
const beforeFocus = (before.artifacts || []).find((item) => norm(item.path || '') === focusPath);
|
|
133
167
|
const affectedPaths = new Set([focusPath, ...((beforeFocus?.downstreamDescendants || []).map((item) => norm(item.path || '')).filter(Boolean))]);
|
|
134
|
-
const lineageBlockers = (after.artifacts || []).filter((item) =>
|
|
168
|
+
const lineageBlockers = (after.artifacts || []).filter((item) => {
|
|
169
|
+
if (!affectedPaths.has(norm(item.path || '')) || item.state === 'healthy') return false;
|
|
170
|
+
if (options.allowQualifiedExternalParentUnresolved === true && norm(item.path || '') === focusPath && item.state === 'parent-unresolved' && hasQualifiedWorkspaceParentReference(replacementMarkdown)) return false;
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
135
173
|
if (lineageBlockers.length) return freeze({ state: 'blocked', reason: 'replacement-shared-lineage-not-clean', blockers: lineageBlockers.map((item) => ({ path: item.path, state: item.state })) });
|
|
136
174
|
return freeze({ state: 'qualified', affectedPaths: [...affectedPaths] });
|
|
137
175
|
}
|
|
138
176
|
|
|
177
|
+
function deterministicReferenceHygieneRepair(record = {}, audit = {}, markdown = '') {
|
|
178
|
+
const source = String(markdown || '');
|
|
179
|
+
if (!source) return freeze({ state: 'unavailable' });
|
|
180
|
+
let candidate = source;
|
|
181
|
+
let parentReferenceChanged = false;
|
|
182
|
+
let schemaReferenceChanged = false;
|
|
183
|
+
const diagnosticCodes = [];
|
|
184
|
+
|
|
185
|
+
const lines = candidate.replace(/\r\n?/g, '\n').split('\n');
|
|
186
|
+
const parentStart = lines.findIndex((line) => /^\s*-\s+Parent\s*$/.test(line));
|
|
187
|
+
if (parentStart >= 0) {
|
|
188
|
+
let parentEnd = lines.length;
|
|
189
|
+
for (let index = parentStart + 1; index < lines.length; index += 1) {
|
|
190
|
+
if (/^-\s+\S/.test(lines[index])) { parentEnd = index; break; }
|
|
191
|
+
}
|
|
192
|
+
for (let index = parentStart + 1; index < parentEnd; index += 1) {
|
|
193
|
+
lines[index] = lines[index].replace(/\]\(([^)]+::[^)]+)\)/g, (whole, target) => {
|
|
194
|
+
const normalized = normalizeMalformedWorkspaceQualifiedTarget(target);
|
|
195
|
+
if (!normalized || normalized === target) return whole;
|
|
196
|
+
parentReferenceChanged = true;
|
|
197
|
+
return `](${normalized})`;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (parentReferenceChanged) {
|
|
202
|
+
candidate = lines.join('\n');
|
|
203
|
+
const integrityLines = candidate.replace(/\r\n?/g, '\n').split('\n');
|
|
204
|
+
const integrityStart = integrityLines.findIndex((line) => line.trim() === '# Continuity Integrity');
|
|
205
|
+
if (integrityStart >= 0) {
|
|
206
|
+
for (let index = integrityStart + 1; index < integrityLines.length; index += 1) {
|
|
207
|
+
integrityLines[index] = integrityLines[index].replace(/\]\(([^)]+::[^)]+)\)/g, (whole, target) => {
|
|
208
|
+
const normalized = normalizeMalformedWorkspaceQualifiedTarget(target);
|
|
209
|
+
return normalized && normalized !== target ? `](${normalized})` : whole;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
candidate = integrityLines.join('\n');
|
|
213
|
+
}
|
|
214
|
+
diagnosticCodes.push('root.parent.recovery.workspace-qualified.malformed', 'portable.lineage-integrity.parent-unresolved');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const exactTarget = String(audit?.schemaValidationAuthority?.currentReference?.target || '').trim();
|
|
218
|
+
const schemaId = String(audit?.schemaId || '').trim();
|
|
219
|
+
const schemaWarning = (audit?.findings || []).some((item) => String(item?.code || '') === 'schema.reference.exact-target-omitted');
|
|
220
|
+
if (schemaWarning && schemaId && exactTarget && audit?.schemaValidationAuthority?.currentReference?.state === 'qualified') {
|
|
221
|
+
const schemaLines = candidate.replace(/\r\n?/g, '\n').split('\n');
|
|
222
|
+
const index = schemaLines.findIndex((line) => /^\s*-\s+Current Schema:\s*/.test(line));
|
|
223
|
+
if (index >= 0) {
|
|
224
|
+
const match = schemaLines[index].match(/^(\s*-\s+Current Schema:\s*)([^\s].*)$/);
|
|
225
|
+
const raw = String(match?.[2] || '').trim();
|
|
226
|
+
if (match && raw === schemaId) {
|
|
227
|
+
schemaLines[index] = `${match[1]}[${schemaId}](${exactTarget})`;
|
|
228
|
+
candidate = schemaLines.join('\n');
|
|
229
|
+
schemaReferenceChanged = true;
|
|
230
|
+
diagnosticCodes.push('schema.reference.exact-target-omitted');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!parentReferenceChanged && !schemaReferenceChanged) return freeze({ state: 'unavailable' });
|
|
236
|
+
const sealed = sealC14nV2Self(candidate);
|
|
237
|
+
if (sealed.state !== 'sealed' && sealed.state !== 'unchanged') return freeze({ state: 'unavailable' });
|
|
238
|
+
return freeze({ state: 'ready', markdown: String(sealed.markdown || candidate), parentReferenceChanged, schemaReferenceChanged, diagnosticCodes: [...new Set(diagnosticCodes)] });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function referenceResolutionsForRecord(resolutions = [], record = {}) {
|
|
242
|
+
const recordPath = norm(record.path || record.id || '');
|
|
243
|
+
return (Array.isArray(resolutions) ? resolutions : []).filter((item) => {
|
|
244
|
+
const itemPath = norm(item?.path || '');
|
|
245
|
+
return !itemPath || itemPath === recordPath;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function projectReferenceResolutionAssistance(markdown = '', resolutions = []) {
|
|
250
|
+
const source = String(markdown || '');
|
|
251
|
+
if (!source || !Array.isArray(resolutions) || !resolutions.length) return freeze({ diagnostics: [], actions: [] });
|
|
252
|
+
const byTarget = new Map(resolutions.map((item) => [String(item?.target || '').trim(), item]).filter(([target]) => Boolean(target)));
|
|
253
|
+
const references = versionBearingGitHubReferences(source).filter((item) => byTarget.has(item.target));
|
|
254
|
+
const diagnostics = [];
|
|
255
|
+
const actions = [];
|
|
256
|
+
for (const reference of references) {
|
|
257
|
+
const fact = byTarget.get(reference.target) || {};
|
|
258
|
+
const exact = fact.exact || {};
|
|
259
|
+
const latest = fact.latest || {};
|
|
260
|
+
const exactState = String(exact.state || 'unavailable');
|
|
261
|
+
const latestState = String(latest.state || 'unavailable');
|
|
262
|
+
const exactSha = String(exact.sha256 || '');
|
|
263
|
+
const latestSha = String(latest.sha256 || '');
|
|
264
|
+
const latestTarget = String(latest.target || '').trim();
|
|
265
|
+
let diagnostic = null;
|
|
266
|
+
let actionTitle = '';
|
|
267
|
+
if (exactState === 'missing') {
|
|
268
|
+
diagnostic = {
|
|
269
|
+
severity: 'error',
|
|
270
|
+
code: 'reference.permalink.unresolved',
|
|
271
|
+
message: `${reference.field} permalink does not resolve at its declared revision.`,
|
|
272
|
+
params: { field: reference.field, line: reference.line }
|
|
273
|
+
};
|
|
274
|
+
if (latestState === 'resolved' && latestTarget) actionTitle = `Repair ${reference.field} permalink to latest`;
|
|
275
|
+
} else if (exactState === 'unavailable') {
|
|
276
|
+
diagnostic = {
|
|
277
|
+
severity: 'warning',
|
|
278
|
+
code: 'reference.permalink.verification-unavailable',
|
|
279
|
+
message: `${reference.field} permalink could not be verified from the current host.`,
|
|
280
|
+
params: { field: reference.field, line: reference.line }
|
|
281
|
+
};
|
|
282
|
+
if (latestState === 'resolved' && latestTarget) actionTitle = `Use latest ${reference.field} permalink`;
|
|
283
|
+
} else if (exactState === 'resolved' && latestState === 'resolved' && exactSha && latestSha && exactSha !== latestSha) {
|
|
284
|
+
diagnostic = {
|
|
285
|
+
severity: 'warning',
|
|
286
|
+
code: 'reference.permalink.stale',
|
|
287
|
+
message: `${reference.field} permalink resolves, but master contains different bytes.`,
|
|
288
|
+
params: { field: reference.field, line: reference.line }
|
|
289
|
+
};
|
|
290
|
+
if (latestTarget) actionTitle = `Upgrade ${reference.field} permalink to latest`;
|
|
291
|
+
}
|
|
292
|
+
if (diagnostic) diagnostics.push(projectDiagnostic(diagnostic, source));
|
|
293
|
+
if (!actionTitle || !latestTarget || latestTarget === reference.target) continue;
|
|
294
|
+
const replacement = replaceReferenceTargetAtLine(source, reference, latestTarget);
|
|
295
|
+
if (!replacement || replacement === source) continue;
|
|
296
|
+
const sealed = sealIfSelfIntegrityPresent(replacement);
|
|
297
|
+
if (sealed.state === 'blocked') continue;
|
|
298
|
+
const replacementMarkdown = sealed.markdown;
|
|
299
|
+
actions.push(freeze({
|
|
300
|
+
id: `upgrade-permalink-${reference.field.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${reference.line}`,
|
|
301
|
+
title: actionTitle,
|
|
302
|
+
kind: 'replace-document',
|
|
303
|
+
qualification: 'deterministic-shared-core+explicit-host-resolution',
|
|
304
|
+
sourceSha256: sha256Hex(new TextEncoder().encode(source)),
|
|
305
|
+
replacementMarkdown,
|
|
306
|
+
diagnosticCodes: diagnostic ? [diagnostic.code] : [],
|
|
307
|
+
boundary: 'Uses explicit host-provided resolution evidence for the exact declared GitHub permalink and master candidate. A newer master revision is only offered as an operator-selected Quick Fix; it does not silently replace version-bearing source authority.'
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
return freeze({ diagnostics, actions });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function versionBearingGitHubReferences(markdown = '') {
|
|
314
|
+
const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
|
|
315
|
+
const refs = [];
|
|
316
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
317
|
+
const line = String(lines[index] || '');
|
|
318
|
+
const fieldMatch = line.match(/^\s*-\s+(Envelope Schema|Parent Schema|Current Schema)\s*:\s*\[[^\]]+\]\((https:\/\/github\.com\/[^)]+\/blob\/[^)]+)\)\s*$/i);
|
|
319
|
+
if (fieldMatch) refs.push({ field: fieldMatch[1], target: fieldMatch[2], line: index + 1 });
|
|
320
|
+
if (/^\s*-\s+\[sha256-base64url-c14n-v2\]\(/.test(line)) {
|
|
321
|
+
const methodMatch = line.match(/^\s*-\s+\[sha256-base64url-c14n-v2\]\((https:\/\/github\.com\/[^)]+\/blob\/[^)]+)\)\s*$/i);
|
|
322
|
+
if (methodMatch) refs.push({ field: 'Integrity method', target: methodMatch[1], line: index + 1 });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return refs;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function replaceReferenceTargetAtLine(markdown = '', reference = {}, latestTarget = '') {
|
|
329
|
+
const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
|
|
330
|
+
const index = Number(reference.line || 0) - 1;
|
|
331
|
+
if (index < 0 || index >= lines.length) return '';
|
|
332
|
+
if (!lines[index].includes(reference.target)) return '';
|
|
333
|
+
lines[index] = lines[index].replace(reference.target, latestTarget);
|
|
334
|
+
return lines.join('\n');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function sealIfSelfIntegrityPresent(markdown = '') {
|
|
338
|
+
const state = canonicalC14nV2SelfState(markdown);
|
|
339
|
+
if (state.state === 'unavailable') return freeze({ state: 'ready', markdown: String(markdown || '') });
|
|
340
|
+
if (!['verified', 'mismatch', 'prepared'].includes(state.state)) return freeze({ state: 'blocked', markdown: String(markdown || '') });
|
|
341
|
+
const sealed = sealC14nV2Self(markdown);
|
|
342
|
+
return sealed.state === 'sealed' ? freeze({ state: 'ready', markdown: sealed.markdown }) : freeze({ state: 'blocked', markdown: String(markdown || '') });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeMalformedWorkspaceQualifiedTarget(value = '') {
|
|
346
|
+
const raw = String(value || '').trim();
|
|
347
|
+
if (classifyParentRecoveryReference(raw).kind !== 'malformed-workspace-qualified') return raw;
|
|
348
|
+
const stripped = raw.replace(/^(?:\.\.\/)+/, '').replace(/^\.\//, '');
|
|
349
|
+
return classifyParentRecoveryReference(stripped).kind === 'workspace-qualified' ? stripped : raw;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function hasQualifiedWorkspaceParentReference(markdown = '') {
|
|
353
|
+
const source = String(markdown || '');
|
|
354
|
+
const parentStart = source.split(/\r?\n/).findIndex((line) => /^\s*-\s+Parent\s*$/.test(line));
|
|
355
|
+
if (parentStart < 0) return false;
|
|
356
|
+
const lines = source.split(/\r?\n/);
|
|
357
|
+
let parentEnd = lines.length;
|
|
358
|
+
for (let index = parentStart + 1; index < lines.length; index += 1) if (/^-\s+\S/.test(lines[index])) { parentEnd = index; break; }
|
|
359
|
+
const targets = [];
|
|
360
|
+
for (let index = parentStart + 1; index < parentEnd; index += 1) {
|
|
361
|
+
for (const match of lines[index].matchAll(/\]\(([^)]+)\)/g)) targets.push(String(match[1] || ''));
|
|
362
|
+
}
|
|
363
|
+
return targets.some((target) => classifyParentRecoveryReference(target).kind === 'workspace-qualified');
|
|
364
|
+
}
|
|
365
|
+
|
|
139
366
|
function projectDiagnostic(finding = {}, markdown = '') {
|
|
140
367
|
const located = locateFindingLine(finding, markdown);
|
|
141
368
|
return freeze({
|
|
@@ -164,6 +391,25 @@ function locatedLine(lines = [], index = -1, state = 'deterministic', basis = ''
|
|
|
164
391
|
export function locateFindingLine(finding = {}, markdown = '') {
|
|
165
392
|
const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
|
|
166
393
|
const params = finding.params || finding;
|
|
394
|
+
const code = String(finding.code || '');
|
|
395
|
+
|
|
396
|
+
// Integrity findings often carry the generic field name `Value`. That field
|
|
397
|
+
// appears once per integrity relation, so resolving `field:Value` first would
|
|
398
|
+
// incorrectly anchor a self-integrity finding on the first Parent digest.
|
|
399
|
+
// Resolve semantically-owned integrity locations before generic field lookup.
|
|
400
|
+
if (code === 'integrity.c14n-v2.mismatch' || code === 'integrity.c14n-v2.ambiguous' || code === 'portable.lineage-integrity.child-self-mismatch' || code === 'portable.lineage-integrity.child-self-unavailable') {
|
|
401
|
+
const selfValueIndex = primarySelfIntegrityValueLine(lines);
|
|
402
|
+
if (selfValueIndex >= 0) return locatedLine(lines, selfValueIndex, 'deterministic', 'continuity-integrity-primary-self-value');
|
|
403
|
+
}
|
|
404
|
+
if (code === 'integrity.method-reference.unqualified') {
|
|
405
|
+
const headingIndex = lines.findIndex((line) => line.trim() === '# Continuity Integrity');
|
|
406
|
+
const methodIndex = lines.findIndex((line, index) => index > headingIndex && /^\s*-\s+\[sha256-base64url-c14n-v2\]\([^)]+\)\s*$/.test(line));
|
|
407
|
+
if (methodIndex >= 0) return locatedLine(lines, methodIndex, 'deterministic', 'continuity-integrity-method-reference');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const explicitLine = Number(params.line || 0);
|
|
411
|
+
if (Number.isInteger(explicitLine) && explicitLine > 0) return locatedLine(lines, explicitLine - 1, 'deterministic', 'explicit-resolution-line');
|
|
412
|
+
|
|
167
413
|
const field = String(params.field || '').trim();
|
|
168
414
|
const section = String(params.section || '').trim();
|
|
169
415
|
const heading = String(params.heading || '').replace(/^#{1,6}\s+/, '').trim();
|
|
@@ -178,19 +424,15 @@ export function locateFindingLine(finding = {}, markdown = '') {
|
|
|
178
424
|
const envelopeIndex = lines.findIndex((line) => new RegExp(`^\\s*-\\s+${escapeRegExp(owner)}(?:\\s*:.*)?\\s*$`, 'i').test(line));
|
|
179
425
|
if (envelopeIndex >= 0) return locatedLine(lines, envelopeIndex, 'deterministic-anchor', `envelope-owner:${owner}`);
|
|
180
426
|
}
|
|
181
|
-
const code = String(finding.code || '');
|
|
182
427
|
if (code.includes('schema.') || code.endsWith('.schema.mismatch') || code === 'audit.schema-authority.unqualified') {
|
|
183
428
|
const index = lines.findIndex((line) => /^\s*-\s+Current Schema\s*:/.test(line));
|
|
184
429
|
if (index >= 0) return locatedLine(lines, index, 'deterministic', 'current-schema-field');
|
|
185
430
|
}
|
|
186
|
-
if (code === 'integrity.method-reference.unqualified') {
|
|
187
|
-
const headingIndex = lines.findIndex((line) => line.trim() === '# Continuity Integrity');
|
|
188
|
-
const methodIndex = lines.findIndex((line, index) => index > headingIndex && /^\s*-\s+\[sha256-base64url-c14n-v2\]\([^)]+\)\s*$/.test(line));
|
|
189
|
-
if (methodIndex >= 0) return locatedLine(lines, methodIndex, 'deterministic', 'continuity-integrity-method-reference');
|
|
190
|
-
}
|
|
191
431
|
if (code.includes('integrity') || /integrity|checksum|digest/i.test(String(finding.message || ''))) {
|
|
192
432
|
const headingIndex = lines.findIndex((line) => line.trim() === '# Continuity Integrity');
|
|
193
433
|
if (headingIndex >= 0) {
|
|
434
|
+
const selfValueIndex = primarySelfIntegrityValueLine(lines);
|
|
435
|
+
if (selfValueIndex >= 0 && /self-integrity|canonical artifact bytes|child-self/i.test(String(finding.message || ''))) return locatedLine(lines, selfValueIndex, 'deterministic', 'continuity-integrity-primary-self-value');
|
|
194
436
|
const index = lines.findIndex((line, i) => i > headingIndex && /^\s+-\s+Value\s*:/.test(line));
|
|
195
437
|
if (index >= 0) return locatedLine(lines, index, 'deterministic', 'continuity-integrity-value');
|
|
196
438
|
return locatedLine(lines, headingIndex, 'deterministic-anchor', 'continuity-integrity-heading');
|
|
@@ -209,6 +451,20 @@ export function locateFindingLine(finding = {}, markdown = '') {
|
|
|
209
451
|
return freeze({ state: 'unresolved', line: null, sourceRange: null, basis: 'shared-finding-has-no-deterministic-line-evidence' });
|
|
210
452
|
}
|
|
211
453
|
|
|
454
|
+
function primarySelfIntegrityValueLine(lines = []) {
|
|
455
|
+
const headingIndex = lines.findIndex((line) => String(line || '').trim() === '# Continuity Integrity');
|
|
456
|
+
if (headingIndex < 0) return -1;
|
|
457
|
+
let selfTowards = -1;
|
|
458
|
+
for (let index = headingIndex + 1; index < lines.length; index += 1) {
|
|
459
|
+
const line = String(lines[index] || '');
|
|
460
|
+
if (index > headingIndex + 1 && /^#\s+/.test(line)) break;
|
|
461
|
+
if (/^\s+-\s+Towards\s*:\s*self\s*$/i.test(line)) { selfTowards = index; continue; }
|
|
462
|
+
if (selfTowards >= 0 && /^\s+-\s+Value\s*:/.test(line)) return index;
|
|
463
|
+
if (selfTowards >= 0 && /^-\s+/.test(line)) selfTowards = -1;
|
|
464
|
+
}
|
|
465
|
+
return -1;
|
|
466
|
+
}
|
|
467
|
+
|
|
212
468
|
function deterministicIntegrityHygieneRepair(markdown = '', findings = []) {
|
|
213
469
|
const source = String(markdown || '');
|
|
214
470
|
const codes = new Set((findings || []).map((item) => String(item?.code || '')));
|
|
@@ -240,7 +496,9 @@ function deterministicIntegrityHygieneRepair(markdown = '', findings = []) {
|
|
|
240
496
|
const diagnosticCodes = [
|
|
241
497
|
...(methodReferenceChanged ? ['integrity.method-reference.unqualified'] : []),
|
|
242
498
|
'integrity.c14n-v2.mismatch',
|
|
243
|
-
'integrity.c14n-v2.ambiguous'
|
|
499
|
+
'integrity.c14n-v2.ambiguous',
|
|
500
|
+
'portable.lineage-integrity.child-self-mismatch',
|
|
501
|
+
'portable.lineage-integrity.child-self-unavailable'
|
|
244
502
|
];
|
|
245
503
|
return freeze({ state: 'ready', markdown: sealed.markdown, methodReferenceChanged, diagnosticCodes: [...new Set(diagnosticCodes)] });
|
|
246
504
|
}
|