@principal-ai/subsystems-core 0.49.2 → 0.51.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.
@@ -14,12 +14,10 @@
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.validateSubsystemModelCrossField = void 0;
16
16
  function isUngrounded(c) {
17
- // Deprecated components are exempt for the same reason proposed ones are: the
18
- // file is a historical pointer to source that is gone, so it cannot resolve
19
- // and its absence is not an inconsistency.
20
- return (c.construct === 'external' ||
21
- c.proposed === true ||
22
- c.deprecated === true);
17
+ // Deprecated components are exempt: the file is a historical pointer to
18
+ // source that is gone, so it cannot resolve and its absence is not an
19
+ // inconsistency.
20
+ return c.construct === 'external' || c.deprecated === true;
23
21
  }
24
22
  /**
25
23
  * `file` is a path *inside the repo named by `purl`* — it resolves against that
@@ -30,8 +28,7 @@ function isUngrounded(c) {
30
28
  * Such a component is a third-party dependency: model it as `construct:
31
29
  * 'external'` with `purl: 'pkg:npm/<package>'` and no file. Applies to externals
32
30
  * too — they carry no file by design, so an install path there is dead weight
33
- * that draws a link nothing can open. `proposed` is exempt like every other
34
- * grounding rule: its file is a placeholder for something not placed yet.
31
+ * that draws a link nothing can open.
35
32
  *
36
33
  * The same rule covers trail step files — a seam into an external belongs
37
34
  * at the call site in the caller, which *is* in the repo.
@@ -78,17 +75,16 @@ function validateSubsystemModelCrossField(doc) {
78
75
  ids.add(c.alias);
79
76
  }
80
77
  // A grounded component that claims a `module` needs a `file` to ground the
81
- // frame. Externals / proposed carry no file by design.
78
+ // frame. Externals carry no file by design.
82
79
  const module = typeof c.module === 'string' ? c.module.trim() : '';
83
80
  const file = typeof c.file === 'string' ? c.file.trim() : '';
84
81
  if (module && !file && !isUngrounded(c)) {
85
82
  problems.push({
86
83
  path: `/components/${i}/module`,
87
- message: `component ${JSON.stringify(c.alias)}: module ${JSON.stringify(module)} is set but file is empty — a module frame needs a file to ground it (mark the component proposed if it is not placed yet).`,
84
+ message: `component ${JSON.stringify(c.alias)}: module ${JSON.stringify(module)} is set but file is empty — a module frame needs a file to ground it.`,
88
85
  });
89
86
  }
90
87
  if (file &&
91
- c.proposed !== true &&
92
88
  c.deprecated !== true &&
93
89
  mentionsNodeModules(file)) {
94
90
  problems.push({
@@ -135,6 +131,42 @@ function validateSubsystemModelCrossField(doc) {
135
131
  const spanProblem = spanRangeProblem(step.span, `/trails/${ti}/steps/${si}/span`, `trail ${JSON.stringify(w.id)}: step ${si}`);
136
132
  if (spanProblem)
137
133
  problems.push(spanProblem);
134
+ // The site construct is the enclosing frame; the seam line must live
135
+ // inside it. The schema can enforce each range but not containment.
136
+ const siteSpanProblem = spanRangeProblem(step.site?.span, `/trails/${ti}/steps/${si}/site/span`, `trail ${JSON.stringify(w.id)}: step ${si} site`);
137
+ if (siteSpanProblem)
138
+ problems.push(siteSpanProblem);
139
+ if (step.span &&
140
+ step.site?.span &&
141
+ !(step.site.span.startLine <= step.span.startLine &&
142
+ step.span.endLine <= step.site.span.endLine)) {
143
+ problems.push({
144
+ path: `/trails/${ti}/steps/${si}/site/span`,
145
+ message: `trail ${JSON.stringify(w.id)}: step ${si} site span ${step.site.span.startLine}-${step.site.span.endLine} does not contain the seam span ${step.span.startLine}-${step.span.endLine}.`,
146
+ });
147
+ }
148
+ // The target is a construct in its own file, so its span and purl
149
+ // fragment are checked independently of the step's site file.
150
+ const targetSpanProblem = spanRangeProblem(step.target?.span, `/trails/${ti}/steps/${si}/target/span`, `trail ${JSON.stringify(w.id)}: step ${si} target`);
151
+ if (targetSpanProblem)
152
+ problems.push(targetSpanProblem);
153
+ if (step.target &&
154
+ typeof step.target.purl === 'string' &&
155
+ step.target.purl.includes('#')) {
156
+ const fragment = step.target.purl.split('#').slice(1).join('#');
157
+ if (fragment !== step.target.file) {
158
+ problems.push({
159
+ path: `/trails/${ti}/steps/${si}/target/purl`,
160
+ message: `trail ${JSON.stringify(w.id)}: step ${si} target purl fragment ${JSON.stringify(fragment)} does not match target file ${JSON.stringify(step.target.file)}`,
161
+ });
162
+ }
163
+ }
164
+ if (step.target && mentionsNodeModules(step.target.file)) {
165
+ problems.push({
166
+ path: `/trails/${ti}/steps/${si}/target/file`,
167
+ message: `trail ${JSON.stringify(w.id)}: step ${si} target file ${JSON.stringify(step.target.file)} points into node_modules — model the dependency as an external instead.`,
168
+ });
169
+ }
138
170
  });
139
171
  });
140
172
  return problems;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/subsystems-core",
3
- "version": "0.49.2",
3
+ "version": "0.51.0",
4
4
  "description": "Subsystem model types and agent-session tooling for Principal AI",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -76,13 +76,9 @@
76
76
  "service"
77
77
  ]
78
78
  },
79
- "proposed": {
80
- "type": "boolean",
81
- "description": "Design / migration placeholder — not a live source declaration yet. Verification skips source checks until promoted. Keep a real construct for the intended shape."
82
- },
83
79
  "deprecated": {
84
80
  "type": "boolean",
85
- "description": "The claim was real but its source is gone upstream — deleted or renamed. The inverse of `proposed`; verification skips source checks the same way, so the component stops blocking verification. Transitional: a cleanup pass removes it and repairs the trails that referenced it."
81
+ "description": "The claim was real but its source is gone upstream — deleted or renamed. Verification skips source checks, so the component stops blocking verification. Transitional: a cleanup pass removes it and repairs the trails that referenced it."
86
82
  },
87
83
  "removedIn": {
88
84
  "type": "object",
@@ -189,9 +185,6 @@
189
185
  "role": {
190
186
  "$ref": "#/$defs/role"
191
187
  },
192
- "proposed": {
193
- "$ref": "#/$defs/proposed"
194
- },
195
188
  "deprecated": {
196
189
  "$ref": "#/$defs/deprecated"
197
190
  },
@@ -272,6 +265,59 @@
272
265
  "$ref": "#/$defs/sourceSpan",
273
266
  "description": "The seam's source span — the call-site range and its content hash. The trail-step counterpart of a component's declarationSpan; captured by verify tooling. `file` + `symbol` locate the frame."
274
267
  },
268
+ "site": {
269
+ "type": "object",
270
+ "description": "The construct containing the seam site, with its full declaration range. Required for `calls`: the caller frame must be housed by the `from` component. For `uses` and data/event seams it is the via-site evidence and is optional. Same file/repo as the step.",
271
+ "additionalProperties": false,
272
+ "required": ["symbol", "span"],
273
+ "properties": {
274
+ "symbol": {
275
+ "type": "string",
276
+ "minLength": 1,
277
+ "description": "Caller/via construct symbol (e.g. `MaintenanceModelList`, `Registry.start`)."
278
+ },
279
+ "span": {
280
+ "$ref": "#/$defs/sourceSpan",
281
+ "description": "Full declaration range of the site construct, not just the seam line."
282
+ },
283
+ "construct": {
284
+ "$ref": "#/$defs/construct",
285
+ "description": "Declaration shape of the site construct, when known."
286
+ }
287
+ }
288
+ },
289
+ "target": {
290
+ "type": "object",
291
+ "description": "The construct the seam reaches. Distinct from `to` (a component alias): the target can be a method on a modeled component, or a construct that is not a node at all.",
292
+ "additionalProperties": false,
293
+ "required": ["purl", "file", "symbol"],
294
+ "properties": {
295
+ "purl": {
296
+ "type": "string",
297
+ "minLength": 1,
298
+ "description": "File-anchored purl of the target repo/file."
299
+ },
300
+ "file": {
301
+ "type": "string",
302
+ "minLength": 1,
303
+ "pattern": "^(?!.*(^|/)node_modules(/|$)).*$",
304
+ "description": "Repo-root-relative target file."
305
+ },
306
+ "symbol": {
307
+ "type": "string",
308
+ "minLength": 1,
309
+ "description": "Callee construct symbol (e.g. `mergeProvenance`, `Store.add`)."
310
+ },
311
+ "span": {
312
+ "$ref": "#/$defs/sourceSpan",
313
+ "description": "Full declaration range of the target construct, when resolvable."
314
+ },
315
+ "construct": {
316
+ "$ref": "#/$defs/construct",
317
+ "description": "Declaration shape of the target, when known."
318
+ }
319
+ }
320
+ },
275
321
  "purl": {
276
322
  "type": "string",
277
323
  "minLength": 1,
@@ -282,16 +328,26 @@
282
328
  "minLength": 1,
283
329
  "description": "Frame name for this step — the function/method on the stack at the site. Required; the Trails list shows this instead of a bare mechanism + filename fallback."
284
330
  },
285
- "proposed": {
286
- "type": "boolean",
287
- "description": "Planned seam — the step describes intended behavior through code that is not written yet. Verification skips source checks; viewers tint it like a proposed node. A step is proposed when it says so OR either endpoint component is proposed."
288
- },
289
331
  "annotation": {
290
332
  "type": "string",
291
333
  "minLength": 1,
292
334
  "description": "Free-text note anchored to this step's site line. Optional, informative only, never verified against source; viewers surface it in the codeview's annotation column."
293
335
  }
294
- }
336
+ },
337
+ "allOf": [
338
+ {
339
+ "description": "A calls step claims the site is the caller frame, so it must carry the enclosing site construct. Other mechanisms may use site as evidence but are not required to.",
340
+ "if": {
341
+ "required": ["mechanism"],
342
+ "properties": {
343
+ "mechanism": { "const": "calls" }
344
+ }
345
+ },
346
+ "then": {
347
+ "required": ["site"]
348
+ }
349
+ }
350
+ ]
295
351
  },
296
352
  "trail": {
297
353
  "type": "object",
package/src/node.ts CHANGED
@@ -22,12 +22,8 @@ export type {
22
22
  TopicUpdate,
23
23
  MigrationResult,
24
24
  } from './storage/topicStore';
25
- export { publishedFromDraft } from './storage/topic-types';
26
25
  export type {
27
26
  DraftTopic,
28
- PublishedTopic,
29
- PublishProjection,
30
- TopicStatus,
31
27
  TopicAsset,
32
28
  TopicCreator,
33
29
  TopicSubsystemModelRef,
@@ -3,78 +3,25 @@
3
3
  * agrees on. Browser-safe (no `node:*`); the file-per-topic store that reads and
4
4
  * writes these lives in `./node` (see `topicStore.ts`).
5
5
  *
6
- * A **topic** is a locally authored subject brief that may be published. Two
7
- * distinct named shapes, split by id-namespace / ownership, with an explicit
8
- * translation between them — NOT one shared shape. Their `id` fields live in
9
- * different namespaces (local vs server), so passing one where the other is
10
- * expected corrupts topic identity.
6
+ * A **topic** is a locally authored subject brief. {@link DraftTopic} is the
7
+ * shape desktop persists to `~/.principal/topics/<id>.json` and edits.
11
8
  *
12
- * - {@link DraftTopic} — locally-authored, what desktop persists + edits.
13
- * - {@link PublishedTopic} — server-authoritative, what the server returns and
14
- * mobile reads.
15
- * - {@link publishedFromDraft} — the Draft -> Published projection (one-way;
16
- * there is no reverse hydration).
17
- *
18
- * All timestamps are ISO 8601 strings because this contract crosses the
19
- * desktop/web boundary.
20
- */
21
-
22
- /**
23
- * Status of a topic — a small structured axis plus optional human nuance.
24
- *
25
- * `state` is the machine-meaningful signal (drives badge color, sorting, and
26
- * filtering). `label` is free-form text shown in place of the default per-state
27
- * label. `waitingOn` describes an external blocker and is meaningful when
28
- * `state` is `waiting`.
29
- *
30
- * The structured shape is deliberate: it lets automations check and resolve a
31
- * blocker without parsing prose — e.g. flip `waiting` -> `paused` once `until`
32
- * passes, or once a referenced PR merges.
9
+ * All timestamps are ISO 8601 strings.
33
10
  */
34
- export interface TopicStatus {
35
- /**
36
- * Structured lifecycle axis, ordered by a feature's "aliveness" from nascent
37
- * to retired. Readers treat an absent OR unrecognized `state` as
38
- * `new-thought`, so legacy topics and renamed values need no migration.
39
- */
40
- state:
41
- 'new-thought' | 'working' | 'paused' | 'waiting' | 'done-for-now' | 'deprecated' | 'abandoned';
42
- /** Free-form text shown in place of the default label for the state. */
43
- label?: string;
44
- /** External blocker description. Meaningful when `state` is `waiting`. */
45
- waitingOn?: {
46
- /** Human description of the blocker, e.g. "design sign-off". */
47
- note?: string;
48
- /** ISO 8601 — when the hold is expected to lift. */
49
- until?: string;
50
- /** Structured pointer to the thing being waited on, for automations. */
51
- ref?: {
52
- kind: 'url' | 'pr' | 'issue' | 'topic';
53
- /** The address/id of the referenced thing. */
54
- value: string;
55
- /** Optional human label for display. */
56
- title?: string;
57
- };
58
- };
59
- }
60
11
 
61
12
  /**
62
13
  * An image attached to a topic — primarily a screenshot dragged into the
63
14
  * description. Bytes live on the topic (referenced from the description via an
64
15
  * `asset://<id>` markdown link), not inlined into the description string. A
65
16
  * render-time resolver swaps `asset://<id>` for `url` (preferred) or a data-URL
66
- * built from `data`, keeping a local (data-only) and a published (url-bearing)
67
- * topic interchangeable on read.
68
- *
69
- * Local-only on a {@link DraftTopic}: assets are uploaded/resolved at publish
70
- * time, so a {@link PublishedTopic} carries the resolved `url`, not raw `data`.
17
+ * built from `data`.
71
18
  */
72
19
  export interface TopicAsset {
73
20
  /** Content hash — free dedup and the stable `asset://` target. */
74
21
  id: string;
75
22
  /** MIME type, e.g. "image/png". */
76
23
  mime: string;
77
- /** Base64-encoded bytes. Present locally and on publish. */
24
+ /** Base64-encoded bytes. */
78
25
  data?: string;
79
26
  /** Resolvable URL, when the bytes have been offloaded (e.g. to S3). */
80
27
  url?: string;
@@ -87,7 +34,7 @@ export interface TopicAsset {
87
34
  /**
88
35
  * GitHub identity of a topic's creator. Populated when a signed-in user
89
36
  * authors a topic; left undefined for local-only topics created before
90
- * sign-in. The server requires this on publish.
37
+ * sign-in.
91
38
  */
92
39
  export interface TopicCreator {
93
40
  githubId: number;
@@ -99,8 +46,8 @@ export interface TopicCreator {
99
46
  * specific runtime trails (flows) within that model.
100
47
  *
101
48
  * The link lives on the **topic** side, in the local store id namespace, and
102
- * never on the model: a subsystem-model document is a portable, host-free
103
- * artifact (shared/published) that must not embed local topic or store ids.
49
+ * never on the model: a subsystem-model document is a portable artifact that
50
+ * must not embed local topic or store ids.
104
51
  */
105
52
  export interface TopicSubsystemModelRef {
106
53
  /** Store-assigned model id, e.g. "sg-1781...". */
@@ -113,14 +60,9 @@ export interface TopicSubsystemModelRef {
113
60
  * Locally-authored topic — the shape desktop persists to
114
61
  * `~/.principal/topics/<id>.json` and edits.
115
62
  *
116
- * Local id namespace. `description` / `createdBy` are optional (a Draft may be
117
- * authored before sign-in). Carries local-only {@link TopicAsset}s
118
- * (`asset://<id>` screenshots, raw `data`). Has NO `visibility` — that's a
119
- * published concept.
120
- *
121
- * A Draft keeps existing after publish (it can be in a "published state"); its
122
- * link to the server identity lives in the desktop's `topics-sync.json`
123
- * sidecar, not in this shape.
63
+ * `description` / `createdBy` are optional (a Draft may be authored before
64
+ * sign-in). Carries local-only {@link TopicAsset}s (`asset://<id>` screenshots,
65
+ * raw `data`).
124
66
  */
125
67
  export interface DraftTopic {
126
68
  /** Unique identifier in the LOCAL id namespace. */
@@ -134,7 +76,7 @@ export interface DraftTopic {
134
76
  * `pkg:github/owner/repo`). The topic's own source of truth for "which repos"
135
77
  * — portable and machine-independent, matching `WorkspaceMembership.repositoryId`.
136
78
  * Machine-local clone paths stay a workspace-membership concern and never
137
- * appear here, so this round-trips across machines on publish/fetch.
79
+ * appear here, so this round-trips across machines.
138
80
  */
139
81
  repos?: string[];
140
82
  /** ISO 8601. */
@@ -143,95 +85,11 @@ export interface DraftTopic {
143
85
  updatedAt: string;
144
86
  /** Creator identity; undefined for topics authored before sign-in. */
145
87
  createdBy?: TopicCreator;
146
- /** Optional workflow status. Absent reads as `new-thought`. */
147
- status?: TopicStatus;
148
88
  /** Local image attachments, referenced from the description via `asset://<id>`. */
149
89
  assets?: TopicAsset[];
150
90
  /**
151
- * Subsystem models this topic concerns, as local store ids. Local-only: not
152
- * carried on publish, since the referenced models are a local artifact.
91
+ * Subsystem models this topic concerns, as local store ids. Local-only, since
92
+ * the referenced models are a local artifact.
153
93
  */
154
94
  subsystemModels?: TopicSubsystemModelRef[];
155
95
  }
156
-
157
- /**
158
- * Server-authoritative topic — the shape the server returns and mobile reads.
159
- *
160
- * Remote id namespace. `description` / `createdBy` are required (publish is the
161
- * validation gate). Carries `visibility`; assets have been resolved to `url`s.
162
- *
163
- * CAVEAT: "Published" means "has a server identity", NOT "public" — a
164
- * PublishedTopic can be `visibility: 'private'`.
165
- */
166
- export interface PublishedTopic {
167
- /** Unique identifier in the REMOTE (server) id namespace. */
168
- id: string;
169
- /** Display title. */
170
- title: string;
171
- /** Markdown body — required on the server. */
172
- description: string;
173
- /**
174
- * Repositories this topic is about, as PURL strings (e.g.
175
- * `pkg:github/owner/repo`). Travels with the topic across the wire so a
176
- * received shared topic carries its repos; preserved on publish/fetch.
177
- */
178
- repos?: string[];
179
- /** ISO 8601. */
180
- createdAt: string;
181
- /** ISO 8601. */
182
- updatedAt: string;
183
- /** Creator identity — required on the server. */
184
- createdBy: TopicCreator;
185
- /** Optional workflow status, travels with the topic when published. */
186
- status?: TopicStatus;
187
- /** Server visibility. "private" is still a PublishedTopic. */
188
- visibility: 'private' | 'public';
189
- }
190
-
191
- /**
192
- * Inputs the {@link DraftTopic} cannot supply on its own — the namespace
193
- * crossing and the fields publish must enforce. The caller resolves these
194
- * before projecting:
195
- * - `createdBy`: required on the server; supplied here if the draft lacks it.
196
- * - `visibility`: a published concept the draft never carries.
197
- */
198
- export interface PublishProjection {
199
- createdBy: TopicCreator;
200
- visibility: 'private' | 'public';
201
- }
202
-
203
- /**
204
- * Project a {@link DraftTopic} into a {@link PublishedTopic} — the one-way
205
- * Draft -> Published translation. It does NOT perform any I/O (no id minting,
206
- * no asset upload, no network): the caller resolves the namespace-crossing and
207
- * required fields into {@link PublishProjection} first (uploading assets,
208
- * choosing visibility), and this enforces the shape.
209
- *
210
- * One-way by design: there is no reverse `PublishedTopic -> DraftTopic`
211
- * hydration. A published topic's edits write through to the server; the local
212
- * Draft is reconciled from the server's response field-by-field, never by
213
- * rebuilding a Draft from a Published shape.
214
- *
215
- * Throws if `description` is empty — publish is also the validation gate, and
216
- * the server rejects a description-less topic.
217
- */
218
- export function publishedFromDraft(
219
- draft: DraftTopic,
220
- projection: PublishProjection
221
- ): PublishedTopic {
222
- const description = (draft.description ?? '').trim();
223
- if (description.length === 0) {
224
- throw new Error(`Topic ${draft.id} has no description; a description is required to publish.`);
225
- }
226
- return {
227
- id: draft.id,
228
- title: draft.title,
229
- description,
230
- ...(draft.repos !== undefined ? { repos: draft.repos } : {}),
231
- createdAt: draft.createdAt,
232
- updatedAt: draft.updatedAt,
233
- createdBy: projection.createdBy,
234
- ...(draft.status !== undefined ? { status: draft.status } : {}),
235
- visibility: projection.visibility,
236
- };
237
- }
@@ -7,7 +7,6 @@ import { mkdtempSync, rmSync, writeFileSync, existsSync, readFileSync } from 'fs
7
7
  import { tmpdir } from 'os';
8
8
  import { join } from 'path';
9
9
  import { TopicStore } from './topicStore';
10
- import { publishedFromDraft, type DraftTopic } from './topic-types';
11
10
 
12
11
  let dir: string;
13
12
  let topicsDir: string;
@@ -74,26 +73,6 @@ describe('CRUD', () => {
74
73
  expect(updated.repos).toEqual(['pkg:github/acme/web', 'pkg:github/acme/api']);
75
74
  });
76
75
 
77
- test('publishedFromDraft carries repos through the projection', () => {
78
- const draft: DraftTopic = {
79
- id: 'topic-pub',
80
- title: 'Pub',
81
- description: 'has a description',
82
- repos: ['pkg:github/acme/web'],
83
- subsystemModels: [{ modelId: 'sg-1-abc', trailIds: ['trail-a'] }],
84
- createdAt: '2026-01-01T00:00:00.000Z',
85
- updatedAt: '2026-01-02T00:00:00.000Z',
86
- };
87
- const published = publishedFromDraft(draft, {
88
- createdBy: { githubId: 1, githubLogin: 'u1' },
89
- visibility: 'private',
90
- });
91
- expect(published.repos).toEqual(['pkg:github/acme/web']);
92
- expect(published).not.toHaveProperty('trailIds');
93
- // Local-only model refs must not travel on publish.
94
- expect(published).not.toHaveProperty('subsystemModels');
95
- });
96
-
97
76
  test('subsystemModels persist on create and are updatable', async () => {
98
77
  const store = makeStore();
99
78
  const created = await store.createTopic({
@@ -188,6 +167,7 @@ describe('index rebuild', () => {
188
167
  const legacy = {
189
168
  ...JSON.parse(readFileSync(topicPath, 'utf8')),
190
169
  trailIds: ['old-trail'],
170
+ status: { state: 'working', label: 'in flight' },
191
171
  description: 'Keep this description',
192
172
  };
193
173
  writeFileSync(topicPath, JSON.stringify(legacy), 'utf8');
@@ -201,6 +181,9 @@ describe('index rebuild', () => {
201
181
  repos: ['pkg:github/a/b'],
202
182
  });
203
183
  expect(loaded).toHaveProperty('trailIds', ['old-trail']);
184
+ // `status` was removed from the contract; legacy files still load and are
185
+ // read back verbatim until the desktop cleans them up.
186
+ expect(loaded).toHaveProperty('status', { state: 'working', label: 'in flight' });
204
187
  expect(JSON.parse(readFileSync(topicPath, 'utf8'))).toHaveProperty('trailIds', ['old-trail']);
205
188
  });
206
189
  });
@@ -27,7 +27,7 @@
27
27
  import { homedir } from 'node:os';
28
28
  import { dirname, join } from 'node:path';
29
29
  import { mkdir, readFile, writeFile, unlink, readdir, rename, access } from 'node:fs/promises';
30
- import type { DraftTopic, TopicStatus } from './topic-types';
30
+ import type { DraftTopic } from './topic-types';
31
31
 
32
32
  /** Root of the global, cross-repo principal narration store. */
33
33
  export const PRINCIPAL_DIR = join(homedir(), '.principal');
@@ -48,8 +48,6 @@ export interface TopicIndexEntry {
48
48
  id: string;
49
49
  title: string;
50
50
  descriptionPreview: string;
51
- /** `status.state` lifted out for filtering/sorting without a file read. */
52
- state?: TopicStatus['state'];
53
51
  createdAt: string;
54
52
  updatedAt: string;
55
53
  createdBy?: { githubId: number; githubLogin: string };
@@ -85,7 +83,6 @@ const buildEntry = (topic: DraftTopic, sizeBytes: number): TopicIndexEntry => ({
85
83
  id: topic.id,
86
84
  title: topic.title || 'Untitled topic',
87
85
  descriptionPreview: descriptionPreview(topic.description),
88
- state: topic.status?.state,
89
86
  createdAt: topic.createdAt,
90
87
  updatedAt: topic.updatedAt,
91
88
  createdBy: topic.createdBy,
@@ -118,7 +115,7 @@ export interface MigrationResult {
118
115
  export type TopicUpdate = Partial<
119
116
  Pick<
120
117
  DraftTopic,
121
- 'title' | 'description' | 'status' | 'createdBy' | 'assets' | 'repos' | 'subsystemModels'
118
+ 'title' | 'description' | 'createdBy' | 'assets' | 'repos' | 'subsystemModels'
122
119
  >
123
120
  >;
124
121
 
@@ -60,6 +60,11 @@ describe("subsystem-model schema", () => {
60
60
  expect(schema.$defs.trailStep?.properties?.line).toBeUndefined();
61
61
  });
62
62
 
63
+ test("trail steps can carry verifiable site and target constructs", () => {
64
+ expect(schema.$defs.trailStep?.properties?.site).toBeDefined();
65
+ expect(schema.$defs.trailStep?.properties?.target).toBeDefined();
66
+ });
67
+
63
68
  test("a store declares its own value type", () => {
64
69
  // `additionalProperties: false` on the store declaration, so the field
65
70
  // has to be declared here or every typed store would be rejected.