@tiinex/core 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +6 -5
  2. package/src/audit/audit.run.js +1 -1
  3. package/src/public/index.js +18 -0
  4. package/src/public/node.js +1 -0
  5. package/src/schemas/creation.contracts.js +1 -1
  6. package/src/schemas/creation.schemaReferences.js +28 -8
  7. package/src/schemas/schema.reference.js +30 -4
  8. package/src/schemas/tiinex.root.v1.schema.json +2 -1
  9. package/src/tooling/portable/adapters/cli/cli.command-input.js +5 -0
  10. package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
  11. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +52 -11
  12. package/src/tooling/portable/adapters/cli/cli.help.js +15 -2
  13. package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
  14. package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +6 -6
  15. package/src/tooling/portable/adapters/cli/cli.source-frontier-reconciliation.js +18 -0
  16. package/src/tooling/portable/adapters/node/bootstrapCarrier.manufacture.js +18 -0
  17. package/src/tooling/portable/adapters/node/handoff.manufacture.enumeration.js +27 -8
  18. package/src/tooling/portable/adapters/node/handoff.manufacture.js +17 -0
  19. package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +2 -1
  20. package/src/tooling/portable/adapters/node/sourceFrontierReconciliationProof.js +23 -0
  21. package/src/tooling/portable/adapters/node/workspaceCarrier.manufacture.js +14 -3
  22. package/src/tooling/portable/audit/audit.capability.js +2 -1
  23. package/src/tooling/portable/bootstrap/tiinex.llm.bootstrap.md +2 -2
  24. package/src/tooling/portable/comparison/sourceFrontierComparison.js +54 -4
  25. package/src/tooling/portable/comparison/sourceFrontierReconciliationProof.js +569 -0
  26. package/src/tooling/portable/draft/draft.create.js +3 -0
  27. package/src/tooling/portable/draft/draft.operations.js +1 -1
  28. package/src/tooling/portable/editor/editor.assistance.js +2 -25
  29. package/src/tooling/portable/handoff/bootstrapCarrier.manufacture.js +56 -0
  30. package/src/tooling/portable/handoff/contracts/tiinex.handoff.package.v1.schema.md +340 -77
  31. package/src/tooling/portable/handoff/manufacture.js +17 -3
  32. package/src/tooling/portable/handoff/pointerEntrypoint.js +4 -1
  33. package/src/tooling/portable/handoff/recipientV2.artifactFirst.build.js +73 -32
  34. package/src/tooling/portable/handoff/recipientV2.artifactFirst.closure.js +1 -1
  35. package/src/tooling/portable/handoff/recipientV2.artifactFirst.inspect.js +9 -1
  36. package/src/tooling/portable/handoff/recipientV2.artifactFirst.materials.js +28 -6
  37. package/src/tooling/portable/handoff/recipientV2.artifactFirst.shared.js +8 -0
  38. package/src/tooling/portable/handoff/recipientV2.coldProjection.js +11 -6
  39. package/src/tooling/portable/handoff/recipientV2.entryContract.js +12 -0
  40. package/src/tooling/portable/handoff/recipientV2.humanOutput.js +48 -6
  41. package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +55 -43
  42. package/src/tooling/portable/handoff/recipientV2.packageV1.contract.js +57 -31
  43. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +63 -10
  44. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +55 -16
  45. package/src/tooling/portable/handoff/recipientV2.packageV1.workspaceProjection.js +14 -1
  46. package/src/tooling/portable/handoff/recipientV2.topology.js +3 -4
  47. package/src/tooling/portable/handoff/recipientV2.topology.materials.js +14 -0
  48. package/src/tooling/portable/handoff/schemaReferencePreflight.js +26 -0
  49. package/src/tooling/portable/handoff/transportEnvelopeV1.js +5 -0
  50. package/src/tooling/portable/handoff/workspaceCarrier.manufacture.js +42 -13
  51. package/src/tooling/portable/index.js +1 -0
  52. package/src/tooling/portable/operation.catalog.js +8 -0
  53. package/src/tooling/portable/source/sourceEligibility.js +108 -0
  54. package/src/validation/validateArtifact.js +62 -6
@@ -2,17 +2,25 @@ import { readdir, readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { sha256Hex } from '../../../../export/package.bytes.js';
4
4
  import { safeWorkspaceToken, serializableMetadata } from './handoff.manufacture.multiRoot.js';
5
+ import {
6
+ DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES,
7
+ DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS,
8
+ portableSourceEligibilityPolicy,
9
+ qualifyPortableSourcePath
10
+ } from '../../source/sourceEligibility.js';
5
11
 
6
12
  export const PORTABLE_NODE_WORKSPACE_ENUMERATION_SCHEMA_ID = 'tiinex.portable.node-workspace-enumeration.v1';
7
- export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES = Object.freeze(['.git', '.tiinex', 'node_modules', '.site-publish', '.release', '.outgoing-handoff-packages']);
8
- export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS = Object.freeze(['.vscode/link']);
13
+ export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES = DEFAULT_PORTABLE_SOURCE_EXCLUDED_DIRECTORIES;
14
+ export const DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS = DEFAULT_PORTABLE_SOURCE_EXCLUDED_RELATIVE_PATHS;
9
15
  const DEFAULT_MAX_FILES = 10000;
10
16
 
11
17
  export async function enumerateNodeWorkspace(rootInput = '.', options = {}) {
12
18
  const root = path.resolve(String(rootInput || '.'));
13
19
  const maxFiles = positiveInteger(options.maxFiles, DEFAULT_MAX_FILES);
14
- const excluded = new Set([...(options.excludeDirectories || DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES)].map(String));
15
- const excludedPaths = new Set(options.excludeRelativePaths || DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS);
20
+ const sourceEligibility = portableSourceEligibilityPolicy({
21
+ excludeDirectories: options.excludeDirectories == null ? DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_DIRECTORIES : options.excludeDirectories,
22
+ excludeRelativePaths: options.excludeRelativePaths == null ? DEFAULT_HANDOFF_MANUFACTURE_EXCLUDED_RELATIVE_PATHS : options.excludeRelativePaths
23
+ });
16
24
  const queue = [root];
17
25
  const absoluteFiles = [];
18
26
  const skippedSymlinks = [];
@@ -21,10 +29,15 @@ export async function enumerateNodeWorkspace(rootInput = '.', options = {}) {
21
29
  const entries = await readdir(current, { withFileTypes: true });
22
30
  entries.sort((a, b) => a.name.localeCompare(b.name));
23
31
  for (const entry of entries) {
24
- if (entry.isDirectory() && excluded.has(entry.name)) continue;
25
32
  const absolute = path.join(current, entry.name);
26
- if (excludedPaths.has(normalizeRelativePath(path.relative(root, absolute)))) continue;
27
- if (entry.isSymbolicLink()) { skippedSymlinks.push(normalizeRelativePath(path.relative(root, absolute))); continue; }
33
+ const relative = normalizeRelativePath(path.relative(root, absolute));
34
+ const eligibility = qualifyPortableSourcePath(relative, {
35
+ excludeDirectories: sourceEligibility.excludedDirectories,
36
+ excludeRelativePaths: sourceEligibility.excludedRelativePaths,
37
+ entryKind: entry.isDirectory() ? 'directory' : 'file'
38
+ });
39
+ if (!eligibility.eligible) continue;
40
+ if (entry.isSymbolicLink()) { skippedSymlinks.push(relative); continue; }
28
41
  if (entry.isDirectory()) queue.push(absolute);
29
42
  else if (entry.isFile()) absoluteFiles.push(absolute);
30
43
  if (absoluteFiles.length > maxFiles) {
@@ -62,7 +75,13 @@ export async function enumerateNodeWorkspace(rootInput = '.', options = {}) {
62
75
  workspaceId,
63
76
  entryCount: includedEntries.length,
64
77
  totalBytes,
65
- exclusions: Object.freeze({ directories: Object.freeze([...excluded].sort()), relativePaths: Object.freeze([...excludedPaths].sort()), symbolicLinks: 'excluded-and-reported' }),
78
+ exclusions: Object.freeze({
79
+ directories: sourceEligibility.excludedDirectories,
80
+ relativePaths: sourceEligibility.excludedRelativePaths,
81
+ fileSuffixes: sourceEligibility.excludedFileSuffixes,
82
+ symbolicLinks: 'excluded-and-reported',
83
+ sourceEligibilitySchema: sourceEligibility.schema
84
+ }),
66
85
  skippedSymlinks: Object.freeze(skippedSymlinks.sort()),
67
86
  entriesFingerprint: sha256Text(stableJson(includedEntries))
68
87
  });
@@ -7,6 +7,8 @@ import { normalizeHandoffCarrierLineage } from '../../handoff/carrierLineage.js'
7
7
  import { normalizeHandoffCarrierProfile } from '../../handoff/carrierProfile.js';
8
8
  import { enumerateNodeWorkspace, PORTABLE_NODE_WORKSPACE_ENUMERATION_SCHEMA_ID } from './handoff.manufacture.enumeration.js';
9
9
  import { preparePackageParentWorkspaceReuse } from './handoff.manufacture.packageParent.js';
10
+ import { qualifyPortableSourceReconciliationProofForManufacture } from '../../comparison/sourceFrontierReconciliationProof.js';
11
+ import { qualifyPortableManufactureSchemaReferenceCandidate } from '../../handoff/schemaReferencePreflight.js';
10
12
  import {
11
13
  assertInside,
12
14
  expandPointerDependencyClosure,
@@ -48,6 +50,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
48
50
  workspaceTitle: requestedWorkspaceTitle,
49
51
  sourceMetadata: input.workspaceSource || input.sourceMetadata || {},
50
52
  excludeDirectories: input.excludeDirectories || options.excludeDirectories,
53
+ excludeRelativePaths: input.excludeRelativePaths || options.excludeRelativePaths,
51
54
  maxFiles: input.maxFiles || options.maxFiles
52
55
  });
53
56
  const additionalWorkspaceDescriptors = normalizeAdditionalWorkspaceDescriptors(input.additionalWorkspaces || input.workspaceRoots || input.workspaceDescriptors || []);
@@ -79,6 +82,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
79
82
  workspaceTitle: requestedTitle,
80
83
  sourceMetadata: descriptor.source || descriptor.sourceMetadata || {},
81
84
  excludeDirectories: descriptor.excludeDirectories || input.excludeDirectories || options.excludeDirectories,
85
+ excludeRelativePaths: descriptor.excludeRelativePaths || input.excludeRelativePaths || options.excludeRelativePaths,
82
86
  maxFiles: descriptor.maxFiles || input.maxFiles || options.maxFiles
83
87
  });
84
88
  if (enumerated.status !== 'qualified-complete') throw new Error(`portable.handoff-manufacture.workspace-enumeration.${id}.${enumerated.status}`);
@@ -97,6 +101,8 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
97
101
  markdown: handoffMarkdown
98
102
  });
99
103
 
104
+ const schemaReferencePreflight = qualifyPortableManufactureSchemaReferenceCandidate(handoff);
105
+
100
106
  if (enumeration.status !== 'qualified-complete') throw new Error(`portable.handoff-manufacture.workspace-enumeration.${enumeration.status}`);
101
107
  const workspaceTitle = requestedWorkspaceTitle || inferWorkspaceTitle(enumeration) || workspaceId;
102
108
  const primaryMaterialization = Object.freeze({ ...enumeration.materialization, title: workspaceTitle });
@@ -138,6 +144,13 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
138
144
  if (targets.length !== 1) throw new Error(`portable.handoff-manufacture.workspace-scope.target-${targets.length ? 'ambiguous' : 'required'}:${materialization.id}`);
139
145
  workspaceMaterializations[index] = projectBoundedWorkspaceMaterialization(materialization, scope, targets[0].path);
140
146
  }
147
+ const hasReconciliationProof = Boolean(input.reconciliationProof && typeof input.reconciliationProof === 'object' && Object.keys(input.reconciliationProof).length);
148
+ const reconciliationProofQualification = qualifyPortableSourceReconciliationProofForManufacture({
149
+ proof: input.reconciliationProof || null,
150
+ requireProof: input.requireReconciliationProof === true,
151
+ workspaceMaterializations,
152
+ requiredWorkspaceIds: hasReconciliationProof || input.requireReconciliationProof === true ? [workspaceId] : []
153
+ });
141
154
 
142
155
  const routeSpecs = transportRoutes.length ? transportRoutes : Object.freeze([{ workspaceId, path: handoffPath }]);
143
156
  let requirements = await projectManufacturingRequirements({ handoff, workspaceId, handoffPath, routeSpecs, workspaceRuntimeById });
@@ -180,11 +193,15 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
180
193
  carrierLineage: normalizeHandoffCarrierLineage(input.carrierLineage || null),
181
194
  carrierProfile: normalizeHandoffCarrierProfile(input.carrierProfile || null),
182
195
  toolingBootstrap: toolingBootstrap.summary,
196
+ reconciliationProofQualification,
197
+ schemaReferencePreflight,
183
198
  manufacturingEvidence: Object.freeze({
184
199
  enumeration: enumeration.evidence,
185
200
  workspaceEnumerations: Object.freeze(workspaceEnumerations),
186
201
  toolingBootstrap: toolingBootstrap.summary,
187
202
  runtimeSourceAlignment,
203
+ reconciliationProof: reconciliationProofQualification,
204
+ schemaReferencePreflight,
188
205
  packageParentWorkspaceReuse: Object.freeze({
189
206
  state: String(packageParentReuse.state || ''),
190
207
  providerState: String(packageParentReuse.providerState || ''),
@@ -59,7 +59,7 @@ async function frontierFromLocalWorkspace(descriptor, options) {
59
59
  id: descriptor.label || `${options.side || 'input'}:${workspaceId}`,
60
60
  source: { kind: 'local-workspace', ref: root, snapshotContract: 'shared-handoff-manufacture-node-enumeration-v1' },
61
61
  workspaces: [workspaceFromEnumeration(workspaceId, enumeration, root)],
62
- boundary: 'Local source is enumerated by the same deterministic Node Workspace enumerator used by Handoff manufacture; excluded directories and symlink policy therefore remain shared.'
62
+ boundary: 'Local source is enumerated by the same deterministic Node Workspace enumerator used by Handoff manufacture; durable-source eligibility, generated-cache exclusions, configured path exclusions, and symlink policy therefore remain shared.'
63
63
  });
64
64
  } catch (error) {
65
65
  return createPortableSourceFrontier({ id: descriptor.label || options.side || '', state: 'qualification-error', source: { kind: 'local-workspace', ref: root }, workspaces: [{ workspaceId, state: 'unavailable', reason: 'local-workspace-read-failed', source: { kind: 'local-workspace', ref: root } }], findings: [portableFinding('error', 'portable.source-frontier.node.local-read-failed', 'Local Workspace source could not be enumerated.', { workspaceId, ref: root, detail: String(error?.message || error || '') })] });
@@ -180,6 +180,7 @@ function enumerationOptions(descriptor, options, workspaceId) {
180
180
  workspaceTitle: String(descriptor.workspaceTitle || descriptor.title || workspaceId),
181
181
  sourceMetadata: descriptor.sourceMetadata || {},
182
182
  excludeDirectories: descriptor.excludeDirectories || options.excludeDirectories,
183
+ excludeRelativePaths: descriptor.excludeRelativePaths || options.excludeRelativePaths,
183
184
  maxFiles: descriptor.maxFiles || options.maxFiles
184
185
  };
185
186
  }
@@ -0,0 +1,23 @@
1
+ import { prepareNodeSourceFrontier } from './sourceFrontierComparison.js';
2
+ import { provePortableSourceReconciliation } from '../../comparison/sourceFrontierReconciliationProof.js';
3
+
4
+ export async function prepareNodeSourceReconciliationProofInput(input = {}, options = {}) {
5
+ const [base, incoming, current, reconciled] = await Promise.all([
6
+ prepareNodeSourceFrontier(input.base, { ...options, side: 'base' }),
7
+ prepareNodeSourceFrontier(input.incoming, { ...options, side: 'incoming' }),
8
+ prepareNodeSourceFrontier(input.current, { ...options, side: 'current' }),
9
+ prepareNodeSourceFrontier(input.reconciled, { ...options, side: 'reconciled' })
10
+ ]);
11
+ return Object.freeze({
12
+ base,
13
+ incoming,
14
+ current,
15
+ reconciled,
16
+ dispositions: input.dispositions || []
17
+ });
18
+ }
19
+
20
+ export async function proveNodeSourceReconciliation(input = {}, options = {}) {
21
+ const normalized = await prepareNodeSourceReconciliationProofInput(input, options);
22
+ return provePortableSourceReconciliation(normalized);
23
+ }
@@ -3,7 +3,7 @@ import { enumerateNodeWorkspace } from './handoff.manufacture.enumeration.js';
3
3
  import { buildToolingBootstrapTransportFiles } from './handoff.manufacture.bootstrap.js';
4
4
  import { qualifyToolingRuntimeSourceAlignment } from './handoff.manufacture.runtimeSource.js';
5
5
  import { inferWorkspaceTitle, normalizeAdditionalWorkspaceDescriptors, safeWorkspaceToken } from './handoff.manufacture.multiRoot.js';
6
- import { normalizeWorkspaceTargetBindings } from './handoff.manufacture.scope.js';
6
+ import { normalizeWorkspaceScopes, normalizeWorkspaceTargetBindings, projectBoundedWorkspaceMaterialization } from './handoff.manufacture.scope.js';
7
7
  import { normalizeHandoffCarrierLineage } from '../../handoff/carrierLineage.js';
8
8
  import { normalizeHandoffCarrierProfile } from '../../handoff/carrierProfile.js';
9
9
 
@@ -29,6 +29,7 @@ export async function prepareNodeWorkspaceCarrierManufacturingInput(input = {},
29
29
  workspaceTitle: descriptor.title,
30
30
  sourceMetadata: descriptor.source || descriptor.sourceMetadata || {},
31
31
  excludeDirectories: descriptor.excludeDirectories || input.excludeDirectories || options.excludeDirectories,
32
+ excludeRelativePaths: descriptor.excludeRelativePaths || input.excludeRelativePaths || options.excludeRelativePaths,
32
33
  maxFiles: descriptor.maxFiles || input.maxFiles || options.maxFiles
33
34
  });
34
35
  if (enumeration.status !== 'qualified-complete') throw new Error(`portable.workspace-carrier.workspace-enumeration.${descriptor.id}.${enumeration.status}`);
@@ -42,6 +43,15 @@ export async function prepareNodeWorkspaceCarrierManufacturingInput(input = {},
42
43
  explicitBindings: input.workspaceTargets || input.workspaceTargetBindings || [],
43
44
  additionalWorkspaceDescriptors
44
45
  });
46
+ const workspaceScopes = normalizeWorkspaceScopes(input.workspaceScopes || input.workspaceScopeBindings || []);
47
+ const materializations = enumerations.map((item) => {
48
+ const materialization = item.materialization;
49
+ const scope = workspaceScopes.get(String(materialization.id || '')) || null;
50
+ if (!scope || scope.coverage !== 'bounded') return materialization;
51
+ const targets = workspaceTargets.filter((target) => String(target.workspaceId || '') === String(materialization.id || ''));
52
+ if (targets.length !== 1) throw new Error(`portable.workspace-carrier.workspace-scope.target-${targets.length ? 'ambiguous' : 'required'}:${materialization.id}`);
53
+ return projectBoundedWorkspaceMaterialization(materialization, scope, targets[0].path);
54
+ });
45
55
  const toolingBootstrap = await buildToolingBootstrapTransportFiles({
46
56
  delivery: input.toolingBootstrap || input.bootstrapDelivery || 'embedded',
47
57
  runtimeRoot: input.runtimeRoot || options.runtimeRoot,
@@ -50,13 +60,14 @@ export async function prepareNodeWorkspaceCarrierManufacturingInput(input = {},
50
60
  });
51
61
  const runtimeSourceAlignment = await qualifyToolingRuntimeSourceAlignment({
52
62
  runtimeIdentity: toolingBootstrap.runtimeIdentity,
53
- localWorkspaces: enumerations.map((item) => Object.freeze({ id: item.materialization.id, root: item.root, materialization: item.materialization })),
63
+ localWorkspaces: enumerations.map((item, index) => Object.freeze({ id: materializations[index].id, root: item.root, materialization: item.materialization })),
54
64
  maxFiles: input.bootstrapMaxFiles || options.bootstrapMaxFiles
55
65
  });
56
66
  return Object.freeze({
57
67
  carrierMode: 'workspace',
58
68
  createdAt: String(input.createdAt || ''),
59
- workspaceMaterializations: Object.freeze(enumerations.map((item) => item.materialization)),
69
+ workspaceMaterializations: Object.freeze(materializations),
70
+ materialRepresentationWorkspaceIds: Object.freeze([...(input.materialRepresentationWorkspaceIds || input.genericMaterialWorkspaceIds || [])].map(String)),
60
71
  workspaceTargets,
61
72
  additionalTransportFiles: toolingBootstrap.files,
62
73
  carrierLineage: normalizeHandoffCarrierLineage(input.carrierLineage || null),
@@ -16,7 +16,8 @@ export function auditPortableRecord(record = {}, options = {}) {
16
16
  record,
17
17
  markdown: record.markdown,
18
18
  validationContractOverride: runtimeProjection?.state === 'qualified' ? runtimeProjection.compiledContract : null,
19
- schemaValidationAuthority: requireExactSchemaAuthority ? runtimeAuthority : null
19
+ schemaValidationAuthority: requireExactSchemaAuthority ? runtimeAuthority : null,
20
+ schemaReferenceContext: options.schemaReferenceContext || 'historical'
20
21
  });
21
22
  } catch (error) {
22
23
  const finding = portableFinding('error', 'portable.audit.exception', error?.message || 'Audit failed.', {
@@ -18,9 +18,9 @@ The same workflow must survive different host arrangements:
18
18
 
19
19
  When this bootstrap or the portable repository is available as a project source, normalize only the explicitly uploaded Markdown, archive, and project-source material, then start with `discover-tooling` or `prepare-task`.
20
20
 
21
- ### Qualified routed Handoff package
21
+ ### Qualified Tiinex package
22
22
 
23
- When a received carrier exposes the qualified recipient-facing Tiinex Handoff package surface, start at `001-1-READ-BEFORE-PROCEEDING.trace.md`. Before Tiinex Tooling is available, the host may perform only the bounded bootstrap exception: read the declared bootstrap node, extract `001-2-bootstrap.zip` into a writable runtime location, and make its Tooling entrypoint callable. Prefer a location that survives turns in the current runtime, but never treat that location as project or artifact authority. Do not manually unpack Workspace/cache payloads or perform broad ZIP/filesystem/repository archaeology. The Tooling entrypoint is exactly `runtime/tools/tiinex-portable.mjs` relative to the chosen extraction root; do not search/list the extracted runtime to discover it. Give the untouched original Handoff-package carrier to that entrypoint and use `orient-handoff-package` as the first semantic operation. Keep the outer Continue-from path opaque to native access. After orientation, use `ground <same-package.zip> --route <same-Continue-from>` as the one shared human/LLM decision-grounding path. Its bounded default receipt composes the selected Handoff authority, exact qualified Required Context identities, the selected-route declared-Parent leaf, a compact declared-Parent continuity-to-qualified-root proof, declared current-work frontier, blockers, and explicit unresolved evidence. Continue substantive bounded work only when `readiness.state` is `grounded-to-act`; `grounded-to-discuss` and `insufficient-grounding` must not be upgraded by generic inference. An apparent loaded root that still declares an unavailable Parent is not cold-start closure: follow `readiness.nextAction.recovery` exactly. If a bound exact repository-read host capability is available, use the projected `plan-host-action` request, normalize the host result through `accept-host-receipt`, then resume the same `ground` command with `--recovery <accepted-host-receipt.json>`; otherwise request exactly the named material from the Transport Operator. When an accepted pinned repository Parent declares a deterministic relative Parent, Tooling may derive only that exact sibling path from the accepted repository/commit/base-path context. For repeated recovery, call `accept-host-receipt --prior <previous-accepted.json>` so the newest accepted result explicitly carries all prior accepted recovery material; pass only that newest cumulative result back to the same `ground --recovery` seam. Never broaden search or treat fetched material as verified before lineage identity/integrity qualification. Re-run the same `ground` command with `--include-required-context <requirement-id,name|all>` only when exact qualified Required Context body text is needed. No external qualification schema, Tooling source inspection, `--help`/operation discovery, context-audit detour, or separate `ground-cold-consumer` call is required before this decision grounding. Native host tools remain valid execution mechanics after Tiinex takeover on already-resolved ordinary source, and explicit degraded fallback only when qualification explains why it was required.
23
+ When a received carrier exposes the qualified recipient-facing Tiinex package surface, start at its declared Start artifact (normally `001-1-READ-BEFORE-PROCEEDING.trace.md`). Before Tiinex Tooling is available, the host may perform only the bounded bootstrap exception: read the declared bootstrap node, extract `001-2-bootstrap.zip` into a writable runtime location, and make its Tooling entrypoint callable. Prefer a location that survives turns in the current runtime, but never treat that location as project or artifact authority. Do not manually unpack Workspace/cache payloads or perform broad ZIP/filesystem/repository archaeology. The Tooling entrypoint is exactly `runtime/tools/tiinex-portable.mjs` relative to the chosen extraction root; do not search/list the extracted runtime to discover it. Give the untouched original package carrier to that entrypoint and use `orient-handoff-package` as the first semantic operation. Package role is qualified from the carried package contract rather than inferred from filenames, Workspace/Role presence, or transport labels. A bootstrap-only or pointerless Workspace carrier has no Handoff route and must not be upgraded into recipient, holder, participation, delegation, current-work, or grounded-to-act authority; use the qualified route-less orientation/material projection only. For a routed Handoff carrier, keep the outer Continue-from path opaque to native access and, after orientation, use `ground <same-package.zip> --route <same-Continue-from>` as the one shared human/LLM decision-grounding path. Its bounded default receipt composes the selected Handoff authority, exact qualified Required Context identities, the selected-route declared-Parent leaf, a compact declared-Parent continuity-to-qualified-root proof, declared current-work frontier, blockers, and explicit unresolved evidence. Continue substantive bounded work only when `readiness.state` is `grounded-to-act`; `grounded-to-discuss` and `insufficient-grounding` must not be upgraded by generic inference. An apparent loaded root that still declares an unavailable Parent is not cold-start closure: follow `readiness.nextAction.recovery` exactly. If a bound exact repository-read host capability is available, use the projected `plan-host-action` request, normalize the host result through `accept-host-receipt`, then resume the same `ground` command with `--recovery <accepted-host-receipt.json>`; otherwise request exactly the named material from the Transport Operator. When an accepted pinned repository Parent declares a deterministic relative Parent, Tooling may derive only that exact sibling path from the accepted repository/commit/base-path context. For repeated recovery, call `accept-host-receipt --prior <previous-accepted.json>` so the newest accepted result explicitly carries all prior accepted recovery material; pass only that newest cumulative result back to the same `ground --recovery` seam. Never broaden search or treat fetched material as verified before lineage identity/integrity qualification. Re-run the same `ground` command with `--include-required-context <requirement-id,name|all>` only when exact qualified Required Context body text is needed. No external qualification schema, Tooling source inspection, `--help`/operation discovery, context-audit detour, or separate `ground-cold-consumer` call is required before this decision grounding. Native host tools remain valid execution mechanics after Tiinex takeover on already-resolved ordinary source, and explicit degraded fallback only when qualification explains why it was required.
24
24
 
25
25
  ### Bootstrap travels inside an archive
26
26
 
@@ -1,5 +1,6 @@
1
1
  import { sha256Hex, utf8Bytes } from '../../../export/package.bytes.js';
2
2
  import { portableFinding, summarizePortableFindings } from '../findings.js';
3
+ import { portableSourceEligibilityPolicy, qualifyPortableSourcePath } from '../source/sourceEligibility.js';
3
4
 
4
5
  export const PORTABLE_SOURCE_FRONTIER_SCHEMA_ID = 'tiinex.portable.source-frontier.v1';
5
6
  export const PORTABLE_SOURCE_FRONTIER_COMPARISON_SCHEMA_ID = 'tiinex.portable.source-frontier-comparison.v1';
@@ -45,16 +46,34 @@ export function createPortableSourceFrontier(input = {}) {
45
46
  workspaces: Object.freeze(workspaces),
46
47
  findings: normalizedFindings,
47
48
  findingSummary: summarizePortableFindings(normalizedFindings),
48
- boundary: String(input.boundary || 'Exact source-byte frontier only. Workspace/path equality does not establish semantic equivalence, authority, acceptance, or merge disposition.')
49
+ boundary: String(input.boundary || 'Eligible durable source-byte frontier only. Source-ineligible transport/runtime/cache bytes are outside frontier identity; Workspace/path equality does not establish semantic equivalence, authority, acceptance, deletion, or merge disposition.')
49
50
  });
50
51
  }
51
52
 
52
53
  export function createPortableWorkspaceSnapshot(entries = [], evidence = {}) {
53
54
  const findings = [];
54
- const normalized = normalizeEntries(entries, findings);
55
+ const selection = normalizeEntries(entries, findings);
55
56
  if (findings.length) return deepFreeze({ state: 'qualification-error', entries: Object.freeze([]), findings: Object.freeze(findings) });
57
+ const normalized = selection.entries;
56
58
  const totalBytes = normalized.reduce((sum, entry) => sum + entry.bytes, 0);
57
59
  const fingerprint = snapshotFingerprint(normalized);
60
+ const inheritedEvidence = serializableObject(evidence);
61
+ const priorEligibility = inheritedEvidence?.sourceEligibility && typeof inheritedEvidence.sourceEligibility === 'object'
62
+ ? inheritedEvidence.sourceEligibility
63
+ : null;
64
+ const upstreamSelection = priorEligibility?.upstreamSelection || (priorEligibility && Number(priorEligibility.excludedEntryCount || 0) > 0 ? portableSourceSelectionReceipt(priorEligibility) : null);
65
+ const sourceEligibility = Object.freeze({
66
+ schema: selection.policy.schema,
67
+ inputEntryCount: selection.inputEntryCount,
68
+ eligibleEntryCount: normalized.length,
69
+ excludedEntryCount: selection.excludedEntryCount,
70
+ excludedByReason: Object.freeze({ ...selection.excludedByReason }),
71
+ excludedDirectories: selection.policy.excludedDirectories,
72
+ excludedRelativePaths: selection.policy.excludedRelativePaths,
73
+ excludedFileSuffixes: selection.policy.excludedFileSuffixes,
74
+ ...(upstreamSelection ? { upstreamSelection: Object.freeze(upstreamSelection) } : {}),
75
+ boundary: selection.policy.boundary
76
+ });
58
77
  return deepFreeze({
59
78
  state: QUALIFIED,
60
79
  entryCount: normalized.length,
@@ -62,7 +81,7 @@ export function createPortableWorkspaceSnapshot(entries = [], evidence = {}) {
62
81
  fingerprint,
63
82
  fingerprintMethod: 'sha256-path-bytes-sha256-v1',
64
83
  entries: Object.freeze(normalized),
65
- evidence: Object.freeze(serializableObject(evidence)),
84
+ evidence: Object.freeze({ ...inheritedEvidence, sourceEligibility }),
66
85
  findings: Object.freeze([])
67
86
  });
68
87
  }
@@ -181,13 +200,28 @@ function normalizeWorkspace(raw, workspaceId, findings) {
181
200
  function normalizeEntries(entries, findings) {
182
201
  const out = [];
183
202
  const seen = new Set();
203
+ const policy = portableSourceEligibilityPolicy();
204
+ const excludedByReason = {};
205
+ let inputEntryCount = 0;
206
+ let excludedEntryCount = 0;
184
207
  for (const raw of entries || []) {
208
+ inputEntryCount += 1;
185
209
  const rawPath = String(raw?.path || raw?.innerPath || '').trim();
186
210
  const path = normalizePath(rawPath);
187
211
  if (unsafeRawPath(rawPath) || !path || unsafePath(path)) {
188
212
  findings.push(portableFinding('error', 'portable.source-frontier.entry.path-invalid', 'Comparable source entry has an unsafe or empty Workspace-relative path.', { path: rawPath }));
189
213
  continue;
190
214
  }
215
+ const eligibility = qualifyPortableSourcePath(path, {
216
+ excludeDirectories: policy.excludedDirectories,
217
+ excludeRelativePaths: policy.excludedRelativePaths,
218
+ entryKind: 'file'
219
+ });
220
+ if (!eligibility.eligible) {
221
+ excludedEntryCount += 1;
222
+ excludedByReason[eligibility.reason] = Number(excludedByReason[eligibility.reason] || 0) + 1;
223
+ continue;
224
+ }
191
225
  if (seen.has(path)) {
192
226
  findings.push(portableFinding('error', 'portable.source-frontier.entry.path-duplicate', 'Comparable source snapshot contains a duplicate normalized path.', { path }));
193
227
  continue;
@@ -202,7 +236,23 @@ function normalizeEntries(entries, findings) {
202
236
  out.push(Object.freeze({ path, bytes, sha256 }));
203
237
  }
204
238
  out.sort((a, b) => a.path.localeCompare(b.path));
205
- return out;
239
+ return Object.freeze({
240
+ entries: Object.freeze(out),
241
+ policy,
242
+ inputEntryCount,
243
+ excludedEntryCount,
244
+ excludedByReason: Object.freeze(Object.fromEntries(Object.entries(excludedByReason).sort(([a], [b]) => a.localeCompare(b))))
245
+ });
246
+ }
247
+
248
+ function portableSourceSelectionReceipt(value = {}) {
249
+ return {
250
+ schema: String(value.schema || ''),
251
+ inputEntryCount: Number(value.inputEntryCount || 0),
252
+ eligibleEntryCount: Number(value.eligibleEntryCount || 0),
253
+ excludedEntryCount: Number(value.excludedEntryCount || 0),
254
+ excludedByReason: Object.freeze({ ...serializableObject(value.excludedByReason || {}) })
255
+ };
206
256
  }
207
257
 
208
258
  function compareWorkspaceUnion(left, right) {