@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.
- package/package.json +6 -5
- package/src/audit/audit.run.js +1 -1
- package/src/public/index.js +18 -0
- package/src/public/node.js +1 -0
- package/src/schemas/creation.contracts.js +1 -1
- package/src/schemas/creation.schemaReferences.js +28 -8
- package/src/schemas/schema.reference.js +30 -4
- package/src/schemas/tiinex.root.v1.schema.json +2 -1
- package/src/tooling/portable/adapters/cli/cli.command-input.js +5 -0
- package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +5 -1
- package/src/tooling/portable/adapters/cli/cli.help.js +15 -2
- package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
- package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +6 -6
- package/src/tooling/portable/adapters/cli/cli.source-frontier-reconciliation.js +18 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.bootstrap.js +37 -1
- package/src/tooling/portable/adapters/node/handoff.manufacture.enumeration.js +27 -8
- package/src/tooling/portable/adapters/node/handoff.manufacture.js +27 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.runtimeSource.js +82 -0
- package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +2 -1
- package/src/tooling/portable/adapters/node/sourceFrontierReconciliationProof.js +23 -0
- package/src/tooling/portable/adapters/node/workspaceCarrier.manufacture.js +8 -1
- package/src/tooling/portable/audit/audit.capability.js +2 -1
- package/src/tooling/portable/comparison/sourceFrontierComparison.js +54 -4
- package/src/tooling/portable/comparison/sourceFrontierReconciliationProof.js +569 -0
- package/src/tooling/portable/draft/draft.create.js +3 -0
- package/src/tooling/portable/draft/draft.operations.js +1 -1
- package/src/tooling/portable/editor/editor.assistance.js +2 -25
- package/src/tooling/portable/handoff/manufacture.js +15 -3
- package/src/tooling/portable/handoff/pointerEntrypoint.js +4 -1
- package/src/tooling/portable/handoff/schemaReferencePreflight.js +26 -0
- package/src/tooling/portable/handoff/transportEnvelopeV1.js +5 -0
- package/src/tooling/portable/index.js +1 -0
- package/src/tooling/portable/operation.catalog.js +8 -0
- package/src/tooling/portable/source/sourceEligibility.js +108 -0
- 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.
|
|
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": "
|
|
171
|
+
"gitHead": "7e705cbd611919cc46659f9bcee0cf363a09c41c",
|
|
171
172
|
"tiinexRelease": {
|
|
172
173
|
"policy": "tiinex.master-npm-release.v1",
|
|
173
|
-
"sourceCommit": "
|
|
174
|
-
"sourceTree": "
|
|
174
|
+
"sourceCommit": "7e705cbd611919cc46659f9bcee0cf363a09c41c",
|
|
175
|
+
"sourceTree": "f6b7df8dfa2757bd05cd4d9f764cd0696cc23797",
|
|
175
176
|
"repository": "Tiinex/core",
|
|
176
|
-
"previousVersion": "0.
|
|
177
|
+
"previousVersion": "0.12.0"
|
|
177
178
|
}
|
|
178
179
|
}
|
package/src/audit/audit.run.js
CHANGED
|
@@ -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',
|
package/src/public/index.js
CHANGED
|
@@ -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';
|
package/src/public/node.js
CHANGED
|
@@ -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:
|
|
12
|
-
current:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
120
|
+
const target = qualifiedExactSchemaReferenceTarget(authority);
|
|
109
121
|
return target ? `[${schemaId}](${target})` : schemaId;
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
export function
|
|
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:
|
|
13
|
-
incoming:
|
|
14
|
-
current:
|
|
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:
|
|
20
|
-
right:
|
|
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
|
|
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 =
|
|
8
|
-
export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS =
|
|
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
|
|
15
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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({
|
|
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
|
});
|