@tiinex/core 0.1.1 → 0.2.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/README.md CHANGED
@@ -31,15 +31,15 @@ Source paths intentionally follow the same `src/...` conventions used by other T
31
31
  repositories where the concepts match. npm distribution preserves this source layout
32
32
  rather than inventing a second directory vocabulary.
33
33
 
34
- ## This source checkpoint
34
+ ## Current implementation frontier
35
35
 
36
- Not yet release-qualified. The public packages are tested headlessly from local npm archives. A full dependency-equipped React/Vite bundle and rendered external-Verse acceptance remain pending. Publishing is disabled in `.github/release-policy.json`.
36
+ `@tiinex/core` is a published package boundary; package publication does not by itself establish browser/product acceptance or semantic authority. App/Site/Verse integration remains separately qualified by those consumers.
37
37
 
38
- The explicit package export map also exposes the shared module paths used by App. These preserve the mirrored layout, not a second implementation. `tools/tiinex-portable.mjs` is retained as the existing bootstrap entrypoint until the later CLI/Interop extraction.
38
+ The explicit package export map exposes the shared module paths used by App. These preserve the mirrored layout, not a second implementation. `tools/tiinex-portable.mjs` remains a transitional compatibility entrypoint while dedicated CLI/Interop hosts are stabilized. See [`docs/ARCHITECTURE-BOUNDARIES.md`](docs/ARCHITECTURE-BOUNDARIES.md) for the carrier-bootstrap, schema-material and external-Interop split.
39
39
 
40
40
  Application data is a declared-data projection: parsing and exact reference resolution are not schema validation, integrity verification or authority qualification. Missing Parent evidence remains unresolved. Companion byte access belongs to registered host readers, not automatic filesystem or network permissions.
41
41
 
42
42
 
43
- ## 2026-09-08 integration frontier
43
+ ## npm release frontier
44
44
 
45
- Master-only automatic versioning/publication is implemented through the single Core Node release helper. See `docs/NPM-PUBLISH.md`; release is disabled until `TIINEX_ENABLE_NPM_PUBLISH` and the npm environment are configured. No registry publication was performed by Anchor.
45
+ Master-only automatic versioning/publication is implemented through the Core Node release helper. See `docs/NPM-PUBLISH.md`. OIDC/Trusted Publisher configuration is an external repository/package setting and remains separate from source qualification.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiinex/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.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,
@@ -165,12 +165,12 @@
165
165
  "type": "git",
166
166
  "url": "git+https://github.com/Tiinex/core.git"
167
167
  },
168
- "gitHead": "9157bef33d676549db915dcb229ecdbf371b748f",
168
+ "gitHead": "7d910295b08a55642306e1a96d7ab913cd428099",
169
169
  "tiinexRelease": {
170
170
  "policy": "tiinex.master-npm-release.v1",
171
- "sourceCommit": "9157bef33d676549db915dcb229ecdbf371b748f",
172
- "sourceTree": "b425dc2882772dce03472d63563ab2e481c2b1b5",
171
+ "sourceCommit": "7d910295b08a55642306e1a96d7ab913cd428099",
172
+ "sourceTree": "901cc01ce64d7565cfae24f832daa84962c7a22f",
173
173
  "repository": "Tiinex/core",
174
- "previousVersion": null
174
+ "previousVersion": "0.1.1"
175
175
  }
176
176
  }
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
1
+ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { parseArtifactMarkdown } from '../../../../artifacts/artifact.parse.js';
4
4
  import { buildArtifactCreationContract } from '../../../../schemas/creation.contracts.js';
@@ -8,6 +8,7 @@ import { sha256Hex, utf8Bytes } from '../../../../export/package.bytes.js';
8
8
  import { resolveSchemaModule } from '../../../../schemas/resolver.js';
9
9
  import { loadNodePortableInput } from '../../input/node.input.js';
10
10
  import { runPortableOperation } from '../../operation.catalog.js';
11
+ import { allocateContinuationPath, allocateDirectoryArtifactPath } from '../../../../transitions/record.transitions.js';
11
12
 
12
13
  const STATE_RELATIVE_PATH = '.tiinex/continuation.json';
13
14
 
@@ -16,21 +17,27 @@ export async function runCommonAuthorCli(parsed = {}, runtime = {}) {
16
17
  const workspaceRoot = path.resolve(String(flags.workspace || parsed.positionals?.[0] || '.'));
17
18
  const state = await readContinuationState(workspaceRoot);
18
19
  const schemaId = String(flags.schema || '').trim();
19
- const artifactRelativePath = normalizeWorkspaceRelativePath(flags.path || parsed.positionals?.[1] || '');
20
+ const requestedArtifactRelativePath = normalizeWorkspaceRelativePath(flags.path || parsed.positionals?.[1] || '');
21
+ const targetDirectory = normalizeWorkspaceRelativePath(flags.directory || flags.dir || '');
20
22
  const bodyPath = String(flags.body || flags.content || '').trim();
21
23
  if (!schemaId) throw new Error('portable.cli.author.schema.required');
22
- if (!artifactRelativePath) throw new Error('portable.cli.author.path.required');
24
+ if (!requestedArtifactRelativePath && !targetDirectory) throw new Error('portable.cli.author.path-or-directory.required');
23
25
  if (!bodyPath) throw new Error('portable.cli.author.body.required');
24
- const artifactPath = safeWorkspaceTarget(workspaceRoot, artifactRelativePath);
25
26
  const bodyMarkdown = (await readFile(path.resolve(bodyPath), 'utf8')).trim();
26
27
  if (!bodyMarkdown) throw new Error('portable.cli.author.body.empty');
28
+ const title = String(flags.title || firstHeading(bodyMarkdown) || state?.roleLabel || schemaId).trim();
27
29
 
28
- const parentRelativePath = resolveParentRelativePath(flags, state);
29
- const parentPath = parentRelativePath ? safeWorkspaceTarget(workspaceRoot, parentRelativePath) : '';
30
- const parentRecord = parentPath ? await parentRecordFromArtifact(parentPath, parentRelativePath, { workspaceRoot, childRelativePath: artifactRelativePath }) : {};
30
+ const parentReference = resolveParentReference(flags, state);
31
+ const parentSource = String(flags['parent-source'] || flags['parent-file'] || '').trim();
32
+ if (isWorkspaceQualifiedReference(parentReference) && !parentSource) throw new Error('portable.cli.author.parent-source.required');
33
+ if (parentSource && !parentReference) throw new Error('portable.cli.author.parent.required');
34
+ if (isWorkspaceQualifiedReference(parentReference) && !requestedArtifactRelativePath && !targetDirectory) throw new Error('portable.cli.author.cross-workspace-parent.target-required');
35
+ const parentPath = parentReference ? (parentSource ? path.resolve(parentSource) : safeWorkspaceTarget(workspaceRoot, parentReference)) : '';
36
+ const artifactRelativePath = requestedArtifactRelativePath || await allocateArtifactRelativePath({ workspaceRoot, targetDirectory, parentRelativePath: parentReference, schemaId, title });
37
+ const artifactPath = safeWorkspaceTarget(workspaceRoot, artifactRelativePath);
38
+ const parentRecord = parentPath ? await parentRecordFromArtifact(parentPath, parentReference, { workspaceRoot, childRelativePath: artifactRelativePath }) : {};
31
39
  const transitionType = String(flags.transition || defaultTransition(schemaId, Boolean(parentPath))).trim();
32
40
  const contract = buildArtifactCreationContract({ schemaId, transitionType });
33
- const title = String(flags.title || firstHeading(bodyMarkdown) || state?.roleLabel || schemaId).trim();
34
41
  const summary = String(flags.summary || title).trim();
35
42
  const authors = String(flags.authors || state?.roleLabel || '').trim();
36
43
  const why = Object.prototype.hasOwnProperty.call(flags, 'why') ? String(flags.why || '').trim() : '';
@@ -94,7 +101,7 @@ export async function runCommonAuthorCli(parsed = {}, runtime = {}) {
94
101
  schema: 'tiinex.portable.common-author.result.v1',
95
102
  operation: 'author',
96
103
  status: 'qualified',
97
- artifact: Object.freeze({ path: artifactRelativePath, absolutePath: artifactPath, schemaId, parentPath: parentRelativePath, selfIntegrity: selfIntegrity.state, written: true }),
104
+ artifact: Object.freeze({ path: artifactRelativePath, absolutePath: artifactPath, schemaId, parentPath: parentReference, parentSource: parentSource || '', selfIntegrity: selfIntegrity.state, written: true }),
98
105
  qualification: Object.freeze({ audit: audit.status, stage: stage.status, exportReady: Boolean(stage?.stagedArtifact?.qualification?.exportReady) }),
99
106
  findingSummary: mergeFindingSummaries(audit?.findingSummary, stage?.findingSummary),
100
107
  nextAction: schemaId === 'tiinex.handoff.v1'
@@ -186,12 +193,62 @@ async function recoverQualifiedLocalSchemaReferenceAuthority(schemaId, context =
186
193
  });
187
194
  }
188
195
 
189
- function resolveParentRelativePath(flags = {}, state = {}) {
196
+ async function allocateArtifactRelativePath({ workspaceRoot, targetDirectory, parentRelativePath, schemaId, title } = {}) {
197
+ const directory = normalizeWorkspaceRelativePath(targetDirectory || (parentRelativePath ? path.posix.dirname(parentRelativePath) : '.topics')) || '.topics';
198
+ const existingPaths = await directoryArtifactPaths(workspaceRoot, directory);
199
+ const allocation = parentRelativePath
200
+ ? allocateContinuationPath({ parentRecord: { path: parentRelativePath }, targetId: schemaId, targetLabel: labelFromSchemaId(schemaId), title }, { targetDirectory: directory, existingPaths })
201
+ : allocateDirectoryArtifactPath({ targetDirectory: directory, targetId: schemaId, targetLabel: labelFromSchemaId(schemaId), title }, { existingPaths });
202
+ const allocated = normalizeWorkspaceRelativePath(allocation?.path || '');
203
+ if (!allocated) throw new Error('portable.cli.author.allocation.unavailable');
204
+ return allocated;
205
+ }
206
+
207
+ async function directoryArtifactPaths(workspaceRoot, directory) {
208
+ const dirPath = safeWorkspaceDirectory(workspaceRoot, directory);
209
+ let entries = [];
210
+ try { entries = await readdir(dirPath, { withFileTypes: true }); }
211
+ catch (error) {
212
+ if (error?.code === 'ENOENT') return [];
213
+ throw error;
214
+ }
215
+ return entries.filter((entry) => entry.isFile()).map((entry) => `${directory}/${entry.name}`);
216
+ }
217
+
218
+ function safeWorkspaceDirectory(root, relative) {
219
+ const normalized = normalizeWorkspaceRelativePath(relative || '.topics') || '.topics';
220
+ const target = path.resolve(root, normalized);
221
+ const rel = path.relative(root, target);
222
+ if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) throw new Error(`portable.cli.author.directory.unsafe:${relative}`);
223
+ return target;
224
+ }
225
+
226
+ function labelFromSchemaId(id = '') {
227
+ const tail = String(id || '').split('.').filter(Boolean).slice(-2, -1)[0] || String(id || 'artifact');
228
+ return tail.charAt(0).toUpperCase() + tail.slice(1);
229
+ }
230
+
231
+ function resolveParentReference(flags = {}, state = {}) {
190
232
  if (flags['no-parent']) return '';
191
- if (typeof flags.parent === 'string' && flags.parent.trim()) return normalizeWorkspaceRelativePath(flags.parent);
233
+ if (typeof flags.parent === 'string' && flags.parent.trim()) return normalizeParentReference(flags.parent);
192
234
  return normalizeWorkspaceRelativePath(state?.lastAuthoredPath || state?.selectedHandoffPath || '');
193
235
  }
194
236
 
237
+ export function normalizeParentReference(value = '') {
238
+ const raw = String(value || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
239
+ if (!raw || raw.startsWith('/') || /^[A-Za-z]:\//.test(raw)) return '';
240
+ const marker = raw.indexOf('::');
241
+ if (marker < 0) return normalizeWorkspaceRelativePath(raw);
242
+ const workspaceId = raw.slice(0, marker).trim();
243
+ const innerPath = normalizeWorkspaceRelativePath(raw.slice(marker + 2));
244
+ if (!workspaceId || !/^[A-Za-z0-9._-]+$/.test(workspaceId) || !innerPath) return '';
245
+ return `${workspaceId}::${innerPath}`;
246
+ }
247
+
248
+ function isWorkspaceQualifiedReference(value = '') {
249
+ return /^[A-Za-z0-9._-]+::/.test(String(value || ''));
250
+ }
251
+
195
252
  function defaultTransition(schemaId, hasParent) {
196
253
  if (!hasParent) return 'create-artifact';
197
254
  if (schemaId === 'tiinex.evidence.v1') return 'reference-record';
@@ -28,6 +28,7 @@ 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 packageParentWorkspaceAliases = await readOptionalJson(flags['package-parent-workspace-aliases']);
31
32
  const operatorCarrierProfile = await readOptionalJson(flags['carrier-profile']);
32
33
  const expectedToolingBootstrap = await readOptionalJson(flags['tooling-bootstrap-manifest']);
33
34
  const workspaceDescriptorValue = await readOptionalJson(flags['workspace-roots'] || flags['workspace-descriptors']);
@@ -124,7 +125,8 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
124
125
  carrierProfile,
125
126
  packageParentBundle,
126
127
  packageParentPath: parentPackagePath ? path.resolve(parentPackagePath) : '',
127
- packageParentSha256
128
+ packageParentSha256,
129
+ packageParentWorkspaceAliases
128
130
  }, runtime);
129
131
  return {
130
132
  input,
@@ -10,7 +10,7 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
10
10
  'Common path (same command for humans and LLMs):',
11
11
  `${command} ground <handoff-package.zip> --route <Continue-from> [--holder-role <recipient-role>]`,
12
12
  `${command} ground <handoff-package.zip> --route <Continue-from> --holder-role <recipient-role> --continue <workspace-dir>`,
13
- `${command} author <workspace-dir> --schema <schema-id> --path <workspace-relative-artifact> --body <body.md> [--parent <workspace-relative-parent>] [--title <title>] [--summary <summary>] [--why <why>]`,
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
  '',
16
16
  `Handoff aliases: ${command} orient <carrier.zip>; ${command} validate <carrier.zip>`,
@@ -20,7 +20,7 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
20
20
  '- `orient`, `ground`, and public `handoff` use compact decision-first default projections; add `--full` on the same command for the complete qualified receipt.',
21
21
  '- `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`.',
22
22
  '- `author` uses continuation state to infer ordinary Parent continuity, seal c14n-v2 integrity, audit, and stage; invalid artifacts are not retained.',
23
- '- `handoff` uses continuation state plus the latest qualified authored Handoff to manufacture the canonical return carrier and excludes runtime-only `.tiinex` state. 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.',
23
+ '- `handoff` uses continuation state plus the latest qualified authored Handoff to manufacture the canonical return carrier and excludes runtime-only `.tiinex` state. When a package parent carries an older Workspace id that has been explicitly renamed, advanced manufacture may bind that predecessor id to a supplied current Workspace 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.',
24
24
  '- Remote reads/writes remain explicit host concerns. Tooling operation safety does not create or revoke semantic Task/Handoff authority.',
25
25
  '',
26
26
  'Use `<common-command> --help` for focused common-path usage. Use `operations` deliberately for the advanced/internal operation catalog.'
@@ -43,9 +43,9 @@ function commonCommandHelp(command, surfaceCommand) {
43
43
  if (surfaceCommand === 'author') return [
44
44
  'Tiinex portable tooling — author',
45
45
  '',
46
- `${command} author <workspace-dir> --schema <schema-id> --path <workspace-relative-artifact> --body <body.md> [--parent <workspace-relative-parent>] [--title <title>] [--summary <summary>] [--why <why>]`,
46
+ `${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>]`,
47
47
  '',
48
- 'Uses qualified continuation state to infer the ordinary Parent when `--parent` is omitted, seals c14n-v2 self-integrity, audits, stages, and updates continuation state only after qualification. Invalid output is not retained.',
48
+ 'Uses qualified continuation state to infer the ordinary Parent when `--parent` is omitted. Supply `--path` for an exact requested coordinate or `--directory` to let Tooling allocate inside that directory-local filename namespace. For a Workspace-qualified Parent such as `business::.topics/...`, also supply `--parent-source` so Tooling reads and seals against the exact Parent bytes without treating the foreign address as a local path. Authoring seals c14n-v2 self-integrity, audits, stages, and updates continuation state only after qualification. Invalid output is not retained.',
49
49
  '',
50
50
  `Advanced/internal catalog: ${command} operations`
51
51
  ];
@@ -67,7 +67,8 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
67
67
  bundle: input.packageParentBundle || null,
68
68
  currentWorkspaceIds: [...seenWorkspaceIds],
69
69
  parentPackagePath: input.packageParentPath || '',
70
- parentPackageSha256: input.packageParentSha256 || ''
70
+ parentPackageSha256: input.packageParentSha256 || '',
71
+ workspaceAliases: input.packageParentWorkspaceAliases || input.workspaceAliases || {}
71
72
  });
72
73
  const additionalEnumerationsPromise = Promise.all(additionalWorkspaceInputs.map(async ({ descriptor, id, root, requestedTitle }) => {
73
74
  const enumerated = await enumerateNodeWorkspace(root, {
@@ -168,6 +169,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
168
169
  state: String(packageParentReuse.state || ''),
169
170
  inspectionStatus: String(packageParentReuse.inspectionStatus || ''),
170
171
  inheritedWorkspaceIds: Object.freeze((packageParentReuse.inherited || []).map((item) => String(item.id || ''))),
172
+ workspaceAliases: Object.freeze([...(packageParentReuse.workspaceAliases || [])].map((item) => Object.freeze({ ...item }))),
171
173
  boundary: String(packageParentReuse.boundary || '')
172
174
  }),
173
175
  carrierProjection: Object.freeze({ requestedRoutes: transportRoutes.length || 1, carrierLineage: normalizeHandoffCarrierLineage(input.carrierLineage || null), carrierProfile: normalizeHandoffCarrierProfile(input.carrierProfile || null), boundary: 'Routes are qualified later against packaged workspace bytes; adapter text is not authority.' })
@@ -6,11 +6,13 @@ export function preparePackageParentWorkspaceReuse(input = {}) {
6
6
  const bundle = input.bundle || null;
7
7
  if (!bundle?.files?.length) return emptyReuse('unavailable');
8
8
  const currentIds = new Set([...(input.currentWorkspaceIds || [])].map(normalizeId).filter(Boolean));
9
+ const workspaceAliases = normalizePackageParentWorkspaceAliases(input.workspaceAliases || input.packageParentWorkspaceAliases || {});
10
+ for (const alias of workspaceAliases.values()) if (!currentIds.has(alias)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-alias.target-unresolved:${alias}`);
9
11
  const inspection = inspectRecipientFacingV2Topology(bundle);
10
12
  const declared = declaredPackageWorkspaceBindings(bundle, inspection);
11
13
  if (!declared.length) return emptyReuse('unsupported-parent-surface');
12
14
  if (inspection.status !== 'valid') throw new Error('portable.handoff-manufacture.package-parent.workspace-provider.invalid');
13
- const missing = declared.filter((item) => !currentIds.has(normalizeId(item.workspaceId)));
15
+ const missing = declared.filter((item) => !packageParentWorkspaceSupersededByCurrent(item.workspaceId, currentIds, workspaceAliases));
14
16
  if (!missing.length) return emptyReuse('not-needed');
15
17
 
16
18
  const providerById = new Map((inspection.workspaceByteProvider?.workspaces || []).map((item) => [normalizeId(item.id), item]));
@@ -39,7 +41,8 @@ export function preparePackageParentWorkspaceReuse(input = {}) {
39
41
  workspaceTargets: Object.freeze(workspaceTargets),
40
42
  inspectionStatus: inspection.status,
41
43
  missingWorkspaceIds: Object.freeze(missing.map((item) => normalizeId(item.workspaceId))),
42
- boundary: 'Exact complete Workspace bytes reused from one independently qualified received package parent. Explicit current Workspace roots take precedence by id; parent-carrier placement and lineage remain non-semantic.'
44
+ workspaceAliases: Object.freeze([...workspaceAliases.entries()].map(([parentWorkspaceId, currentWorkspaceId]) => Object.freeze({ parentWorkspaceId, currentWorkspaceId }))),
45
+ boundary: 'Exact complete Workspace bytes reused from one independently qualified received package parent. Explicit current Workspace roots take precedence by id, and explicit qualified workspace aliases may supersede a renamed parent-carrier Workspace without carrying stale duplicate source. Parent-carrier placement and lineage remain non-semantic.'
43
46
  });
44
47
  }
45
48
 
@@ -121,8 +124,34 @@ function buildInheritedEnumeration(provider = {}, source = {}) {
121
124
  });
122
125
  }
123
126
 
127
+ export function normalizePackageParentWorkspaceAliases(value = {}) {
128
+ const entries = Array.isArray(value)
129
+ ? value.map((item) => [item?.parentWorkspaceId || item?.parent || item?.from, item?.currentWorkspaceId || item?.current || item?.to])
130
+ : Object.entries(value || {});
131
+ const out = new Map();
132
+ const targets = new Set();
133
+ for (const [parentValue, currentValue] of entries) {
134
+ const parentWorkspaceId = normalizeId(parentValue);
135
+ const currentWorkspaceId = normalizeId(currentValue);
136
+ if (!parentWorkspaceId || !currentWorkspaceId) throw new Error('portable.handoff-manufacture.package-parent.workspace-alias.invalid');
137
+ if (parentWorkspaceId === currentWorkspaceId) throw new Error(`portable.handoff-manufacture.package-parent.workspace-alias.identity:${parentWorkspaceId}`);
138
+ if (out.has(parentWorkspaceId)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-alias.duplicate-parent:${parentWorkspaceId}`);
139
+ if (targets.has(currentWorkspaceId)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-alias.duplicate-target:${currentWorkspaceId}`);
140
+ out.set(parentWorkspaceId, currentWorkspaceId);
141
+ targets.add(currentWorkspaceId);
142
+ }
143
+ return out;
144
+ }
145
+
146
+ export function packageParentWorkspaceSupersededByCurrent(parentWorkspaceId = '', currentWorkspaceIds = new Set(), workspaceAliases = new Map()) {
147
+ const id = normalizeId(parentWorkspaceId);
148
+ if (currentWorkspaceIds.has(id)) return true;
149
+ const replacement = workspaceAliases.get(id) || '';
150
+ return Boolean(replacement && currentWorkspaceIds.has(replacement));
151
+ }
152
+
124
153
  function emptyReuse(state) {
125
- return Object.freeze({ state, inherited: Object.freeze([]), workspaceTargets: Object.freeze([]), inspectionStatus: '', missingWorkspaceIds: Object.freeze([]), boundary: 'No package-parent Workspace provider reuse was required.' });
154
+ return Object.freeze({ state, inherited: Object.freeze([]), workspaceTargets: Object.freeze([]), inspectionStatus: '', missingWorkspaceIds: Object.freeze([]), workspaceAliases: Object.freeze([]), boundary: 'No package-parent Workspace provider reuse was required.' });
126
155
  }
127
156
  function normalizeId(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''); }
128
157
  function mediaTypeForPath(value = '') { const lower = String(value || '').toLowerCase(); if (lower.endsWith('.md')) return 'text/markdown'; if (lower.endsWith('.json')) return 'application/json'; if (/\.(?:m?js|cjs)$/.test(lower)) return 'text/javascript'; if (lower.endsWith('.ts')) return 'text/typescript'; if (lower.endsWith('.css')) return 'text/css'; if (lower.endsWith('.html')) return 'text/html'; if (/\.(?:yml|yaml)$/.test(lower)) return 'text/yaml'; if (lower.endsWith('.txt')) return 'text/plain'; return 'application/octet-stream'; }
@@ -167,29 +167,59 @@ export function allocateContinuationPath({ parentRecord = {}, targetId = '', tar
167
167
  if (explicitPath) return { path: uniqueTransitionPath(explicitPath, occupied), policy: pathPolicyForExplicit(explicitPath) };
168
168
  const parentPath = externalWebArtifactUrl(parentRecord) ? '' : canonicalLocalPath(parentRecord.path || parentRecord.sourcePath || parentRecord.sourceTarget?.sourceArtifactPath || '');
169
169
  const parentDir = parentDirectory(parentPath) || '.topics';
170
+ const requestedDir = canonicalLocalPath(options.targetDirectory || options.directory || '');
171
+ const targetDir = requestedDir || parentDir;
170
172
  const parentPrefix = lineagePrefixFromPath(parentPath);
171
173
  const labelSlug = slugify(title || parentRecord.title || targetLabel || 'continuation');
172
174
  const targetSlug = slugify(targetLabel || labelFromSchemaId(targetId) || 'leaf');
173
175
  const extension = '.trace.md';
176
+ const directoryLocal = Boolean(requestedDir && requestedDir !== parentDir);
174
177
  const policy = {
175
178
  schema: 'tiinex.transition.path-policy.v1',
176
- kind: 'same-parent-directory',
179
+ kind: directoryLocal ? 'directory-local-continuation' : 'same-parent-directory',
177
180
  parentDirectory: parentDir,
181
+ targetDirectory: targetDir,
178
182
  parentPath,
179
183
  parentLineagePrefix: parentPrefix,
180
184
  labelSlug,
181
185
  targetSlug,
182
- extension
186
+ extension,
187
+ allocationAuthority: directoryLocal ? 'target-directory-local-namespace' : 'same-parent-directory-continuation'
188
+ };
189
+ return { path: pathFromPolicy(policy, occupied), policy };
190
+ }
191
+
192
+ export function allocateDirectoryArtifactPath({ targetDirectory = '.topics', targetId = '', targetLabel = '', title = '' } = {}, options = {}) {
193
+ const occupied = existingTransitionPaths(options);
194
+ const explicitPath = canonicalLocalPath(options.path || options.draftPath || '');
195
+ if (explicitPath) return { path: uniqueTransitionPath(explicitPath, occupied), policy: pathPolicyForExplicit(explicitPath) };
196
+ const dir = canonicalLocalPath(targetDirectory || '.topics') || '.topics';
197
+ const labelSlug = slugify(title || targetLabel || labelFromSchemaId(targetId) || 'artifact');
198
+ const targetSlug = slugify(targetLabel || labelFromSchemaId(targetId) || 'artifact');
199
+ const extension = '.trace.md';
200
+ const policy = {
201
+ schema: 'tiinex.transition.path-policy.v1',
202
+ kind: 'directory-local-root',
203
+ targetDirectory: dir,
204
+ labelSlug,
205
+ targetSlug,
206
+ extension,
207
+ allocationAuthority: 'target-directory-local-namespace'
183
208
  };
184
209
  return { path: pathFromPolicy(policy, occupied), policy };
185
210
  }
186
211
 
187
212
  function pathFromPolicy(policy = {}, occupied = new Set()) {
188
- const dir = canonicalLocalPath(policy.parentDirectory || '.topics') || '.topics';
213
+ const kind = String(policy.kind || '').trim();
214
+ const dir = canonicalLocalPath(policy.targetDirectory || policy.parentDirectory || '.topics') || '.topics';
189
215
  const extension = String(policy.extension || '.trace.md').startsWith('.') ? String(policy.extension || '.trace.md') : `.${policy.extension}`;
190
216
  const labelSlug = slugify(policy.labelSlug || 'continuation');
191
217
  const targetSlug = slugify(policy.targetSlug || 'leaf');
192
218
  const parentPrefix = String(policy.parentLineagePrefix || '').trim();
219
+ if (kind === 'directory-local-root' || kind === 'directory-local-continuation') {
220
+ const rootPrefix = nextDirectoryRootLineagePrefix(dir, occupied);
221
+ return `${dir}/${rootPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
222
+ }
193
223
  if (parentPrefix) {
194
224
  const childPrefix = nextChildLineagePrefix(parentPrefix, dir, occupied);
195
225
  return `${dir}/${childPrefix}-${labelSlug}.${extension.replace(/^\./, '')}`;
@@ -226,6 +256,25 @@ function lineagePrefixFromPath(path = '') {
226
256
  return '';
227
257
  }
228
258
 
259
+
260
+ function nextDirectoryRootLineagePrefix(dir = '', occupied = new Set()) {
261
+ const numbers = [];
262
+ let width = 3;
263
+ for (const value of occupied || []) {
264
+ const canonical = canonicalLocalPath(value);
265
+ if (parentDirectory(canonical) !== dir) continue;
266
+ const name = basenameWithoutKnownMarkdownExtension(canonical);
267
+ const match = name.match(/^(\d+)(?:-|$)/);
268
+ if (!match || /^20\d{2}$/.test(match[1])) continue;
269
+ const root = match[1].split('-')[0];
270
+ if (!/^\d+$/.test(root)) continue;
271
+ numbers.push(Number(root));
272
+ width = Math.max(width, root.length);
273
+ }
274
+ const next = numbers.length ? Math.max(...numbers) + 1 : 1;
275
+ return String(next).padStart(width, '0');
276
+ }
277
+
229
278
  function nextChildLineagePrefix(parentPrefix = '', dir = '', occupied = new Set()) {
230
279
  const prefix = String(parentPrefix || '').trim();
231
280
  const width = childOrdinalWidth(prefix);
@@ -1,43 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { readFile } from 'node:fs/promises';
3
-
4
- const pointer = JSON.parse(await readFile(new URL('./tiinex.llm.bootstrap.pointer.json', import.meta.url), 'utf8'));
5
- const bootstrap = await readFile(new URL('./tiinex.llm.bootstrap.md', import.meta.url), 'utf8');
6
-
7
- assert.equal(pointer.schema, 'tiinex.portable.bootstrap.pointer.v1');
8
- assert.equal(pointer.repository, 'Tiinex/site');
9
- assert.equal(pointer.firstOperations.includes('discover-tooling'), true);
10
- assert.equal(pointer.firstOperations.includes('plan-host-action'), true);
11
- assert.equal(pointer.boundary.remoteWrite, false);
12
- assert.equal(pointer.boundary.sourceMutation, false);
13
- assert.equal(bootstrap.includes('## Startup Modes'), true);
14
- assert.equal(bootstrap.includes('Pre-prompt only; no bootstrap source is loaded'), true);
15
- assert.equal(bootstrap.includes(pointer.bootstrapPath), true);
16
- assert.equal(bootstrap.includes('do not execute package code'), true);
17
- assert.equal(bootstrap.includes('## Bind Capabilities To Concrete Host Tools'), true);
18
- assert.equal(bootstrap.includes('tiinex.portable.host-action-receipt.v1'), true);
19
- assert.equal(bootstrap.includes('## Manufacture Recipient-Relative Handoff Packages'), true);
20
- assert.equal(bootstrap.includes('manufacture-handoff-package'), true);
21
- assert.equal(bootstrap.includes('001-1-READ-BEFORE-PROCEEDING.trace.md'), true);
22
- assert.equal(bootstrap.includes('001-<package-slug>.trace.md'), true);
23
- assert.equal(bootstrap.includes('Generated Markdown children declare package-local `Parent` continuity'), true);
24
- assert.equal(bootstrap.includes('Exact durable source artifacts inside Workspace/cache archives retain their own historical provenance'), true);
25
- assert.equal(bootstrap.includes('001-0-READ-BEFORE-PROCEEDING.trace.md'), false);
26
- assert.equal(bootstrap.includes('tiinex.package/START.md'), false);
27
- assert.equal(bootstrap.includes('orient-handoff-package'), true);
28
- assert.equal(bootstrap.includes('audit-handoff-package-context'), true);
29
- assert.equal(bootstrap.includes('inspect ./received --summary --phase-timing'), true);
30
- assert.equal(bootstrap.includes('audit ./received --summary --phase-timing'), true);
31
- assert.equal(bootstrap.includes('search-lineage ./received --query \"mobile overflow\" --relation leaf --summary --phase-timing'), true);
32
- assert.equal(bootstrap.includes('resolve-lineage ./received --depth 3 --direction both --summary --phase-timing'), true);
33
- assert.equal(bootstrap.includes('npm run tooling:search -- --query'), true);
34
- assert.equal(bootstrap.includes('--include-legacy-topics'), true);
35
- assert.equal(bootstrap.includes('--include-legacy-fixtures'), true);
36
- assert.equal(bootstrap.includes('ground ... --include-required-context all'), true);
37
- assert.equal(bootstrap.includes('A receipt that omits Required Context bodies is not proof that the model has read those bodies.'), true);
38
- assert.equal(bootstrap.includes('humanOutput.normalInlineRouting.content'), true);
39
- assert.equal(bootstrap.includes('sole primary package'), true);
40
- assert.equal(bootstrap.includes('Filename or co-location under a bootstrap-like path does not grant bootstrap authority.'), true);
41
- assert.equal(bootstrap.includes('canonical Handoff semantic authoring/validation or a locked canonical package schema'), true);
42
-
43
- console.log('✓ portable bootstrap pointer and startup-mode contract passed');