@ontrails/core 1.0.0-beta.13 → 1.0.0-beta.14

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 (59) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +2 -1
  4. package/dist/derive.d.ts +6 -0
  5. package/dist/derive.d.ts.map +1 -1
  6. package/dist/derive.js +29 -5
  7. package/dist/derive.js.map +1 -1
  8. package/dist/draft.d.ts +28 -0
  9. package/dist/draft.d.ts.map +1 -0
  10. package/dist/draft.js +156 -0
  11. package/dist/draft.js.map +1 -0
  12. package/dist/index.d.ts +6 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +5 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/internal/topo-saves.d.ts +47 -0
  17. package/dist/internal/topo-saves.d.ts.map +1 -0
  18. package/dist/internal/topo-saves.js +310 -0
  19. package/dist/internal/topo-saves.js.map +1 -0
  20. package/dist/internal/topo-store-read.d.ts +67 -0
  21. package/dist/internal/topo-store-read.d.ts.map +1 -0
  22. package/dist/internal/topo-store-read.js +222 -0
  23. package/dist/internal/topo-store-read.js.map +1 -0
  24. package/dist/internal/topo-store.d.ts +12 -0
  25. package/dist/internal/topo-store.d.ts.map +1 -0
  26. package/dist/internal/topo-store.js +571 -0
  27. package/dist/internal/topo-store.js.map +1 -0
  28. package/dist/internal/trails-db.d.ts +16 -0
  29. package/dist/internal/trails-db.d.ts.map +1 -0
  30. package/dist/internal/trails-db.js +118 -0
  31. package/dist/internal/trails-db.js.map +1 -0
  32. package/dist/topo-store.d.ts +48 -0
  33. package/dist/topo-store.d.ts.map +1 -0
  34. package/dist/topo-store.js +175 -0
  35. package/dist/topo-store.js.map +1 -0
  36. package/dist/validate-established-topo.d.ts +76 -0
  37. package/dist/validate-established-topo.d.ts.map +1 -0
  38. package/dist/validate-established-topo.js +43 -0
  39. package/dist/validate-established-topo.js.map +1 -0
  40. package/dist/validate-topo.d.ts.map +1 -1
  41. package/dist/validate-topo.js +5 -3
  42. package/dist/validate-topo.js.map +1 -1
  43. package/package.json +4 -1
  44. package/src/__tests__/derive.test.ts +58 -1
  45. package/src/__tests__/topo-store-read.test.ts +251 -0
  46. package/src/__tests__/topo-store.test.ts +469 -0
  47. package/src/__tests__/trails-db.test.ts +191 -0
  48. package/src/__tests__/validate-topo.test.ts +167 -0
  49. package/src/derive.ts +39 -8
  50. package/src/draft.ts +334 -0
  51. package/src/index.ts +30 -1
  52. package/src/internal/topo-saves.ts +429 -0
  53. package/src/internal/topo-store-read.ts +473 -0
  54. package/src/internal/topo-store.ts +1087 -0
  55. package/src/internal/trails-db.ts +189 -0
  56. package/src/topo-store.ts +301 -0
  57. package/src/validate-established-topo.ts +63 -0
  58. package/src/validate-topo.ts +7 -3
  59. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,189 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+
5
+ import { NotFoundError } from '../errors.js';
6
+
7
+ const TRAILS_DIR = '.trails';
8
+ const TRAILS_DB_FILE = 'trails.db';
9
+ const SCHEMA_VERSION_TABLE = 'meta_schema_versions';
10
+ const WORKSPACE_SUBDIRS = ['config', 'dev', 'generated'] as const;
11
+ const REQUIRED_GITIGNORE_LINES = [
12
+ '# Local config overrides',
13
+ 'config/',
14
+ '',
15
+ '# Development state',
16
+ 'dev/',
17
+ '',
18
+ '# Generated artifacts',
19
+ 'generated/',
20
+ '',
21
+ '# Shared Trails database',
22
+ 'trails.db',
23
+ 'trails.db-shm',
24
+ 'trails.db-wal',
25
+ '',
26
+ ];
27
+
28
+ export interface TrailsDbLocationOptions {
29
+ readonly path?: string;
30
+ readonly rootDir?: string;
31
+ }
32
+
33
+ export interface EnsureSubsystemSchemaOptions {
34
+ readonly migrate: (currentVersion: number) => void;
35
+ readonly subsystem: string;
36
+ readonly version: number;
37
+ }
38
+
39
+ interface SchemaVersionRow {
40
+ readonly version: number;
41
+ }
42
+
43
+ const resolveRootDir = (rootDir?: string): string =>
44
+ resolve(rootDir ?? process.cwd());
45
+
46
+ export const resolveTrailsDir = (options?: TrailsDbLocationOptions): string =>
47
+ join(resolveRootDir(options?.rootDir), TRAILS_DIR);
48
+
49
+ export const resolveTrailsDbPath = (
50
+ options?: TrailsDbLocationOptions
51
+ ): string =>
52
+ options?.path
53
+ ? resolve(options.path)
54
+ : join(resolveTrailsDir(options), TRAILS_DB_FILE);
55
+
56
+ const ensureDbParentDir = (dbPath: string): void => {
57
+ mkdirSync(dirname(dbPath), { recursive: true });
58
+ };
59
+
60
+ const GITIGNORE_TEMPLATE = `${REQUIRED_GITIGNORE_LINES.join('\n').trimEnd()}\n`;
61
+
62
+ const appendMissingGitignoreLines = (
63
+ gitignorePath: string,
64
+ content: string
65
+ ): void => {
66
+ const existingLines = new Set(content.split('\n').map((l) => l.trim()));
67
+ const missing = REQUIRED_GITIGNORE_LINES.filter(
68
+ (line) => line !== '' && !existingLines.has(line)
69
+ );
70
+
71
+ if (missing.length === 0) {
72
+ return;
73
+ }
74
+
75
+ const next = `${content.trimEnd()}\n\n${missing.join('\n')}`;
76
+ writeFileSync(gitignorePath, `${next.trimEnd()}\n`);
77
+ };
78
+
79
+ const ensureWorkspaceGitignore = (trailsDir: string): void => {
80
+ const gitignorePath = join(trailsDir, '.gitignore');
81
+
82
+ if (!existsSync(gitignorePath)) {
83
+ writeFileSync(gitignorePath, GITIGNORE_TEMPLATE);
84
+ return;
85
+ }
86
+
87
+ appendMissingGitignoreLines(
88
+ gitignorePath,
89
+ readFileSync(gitignorePath, 'utf8')
90
+ );
91
+ };
92
+
93
+ const ensureWorkspaceDir = (rootDir: string): void => {
94
+ const trailsDir = resolveTrailsDir({ rootDir });
95
+ mkdirSync(trailsDir, { recursive: true });
96
+ for (const subdir of WORKSPACE_SUBDIRS) {
97
+ mkdirSync(join(trailsDir, subdir), { recursive: true });
98
+ }
99
+ ensureWorkspaceGitignore(trailsDir);
100
+ };
101
+
102
+ const initializeWritePragmas = (db: Database): void => {
103
+ db.run('PRAGMA journal_mode = WAL');
104
+ db.run('PRAGMA synchronous = NORMAL');
105
+ db.run('PRAGMA foreign_keys = ON');
106
+ };
107
+
108
+ const ensureSchemaVersionTable = (db: Database): void => {
109
+ db.run(`CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE} (
110
+ subsystem TEXT PRIMARY KEY,
111
+ version INTEGER NOT NULL,
112
+ updated_at TEXT NOT NULL
113
+ )`);
114
+ };
115
+
116
+ const readSubsystemVersion = (db: Database, subsystem: string): number => {
117
+ const row = db
118
+ .query<SchemaVersionRow, [string]>(
119
+ `SELECT version FROM ${SCHEMA_VERSION_TABLE} WHERE subsystem = ?`
120
+ )
121
+ .get(subsystem);
122
+ return row?.version ?? 0;
123
+ };
124
+
125
+ const writeSubsystemVersion = (
126
+ db: Database,
127
+ subsystem: string,
128
+ version: number
129
+ ): void => {
130
+ db.run(
131
+ `INSERT INTO ${SCHEMA_VERSION_TABLE} (subsystem, version, updated_at)
132
+ VALUES (?, ?, ?)
133
+ ON CONFLICT(subsystem) DO UPDATE SET
134
+ version = excluded.version,
135
+ updated_at = excluded.updated_at`,
136
+ [subsystem, version, new Date().toISOString()]
137
+ );
138
+ };
139
+
140
+ export const openWriteTrailsDb = (
141
+ options?: TrailsDbLocationOptions
142
+ ): Database => {
143
+ const rootDir = resolveRootDir(options?.rootDir);
144
+ const dbPath = resolveTrailsDbPath(
145
+ options?.path ? { path: options.path, rootDir } : { rootDir }
146
+ );
147
+
148
+ if (options?.path === undefined) {
149
+ ensureWorkspaceDir(rootDir);
150
+ } else {
151
+ ensureDbParentDir(dbPath);
152
+ }
153
+
154
+ const db = new Database(dbPath, { create: true });
155
+ initializeWritePragmas(db);
156
+ ensureSchemaVersionTable(db);
157
+ return db;
158
+ };
159
+
160
+ export const openReadTrailsDb = (
161
+ options?: TrailsDbLocationOptions
162
+ ): Database => {
163
+ const dbPath = resolveTrailsDbPath(options);
164
+ if (!existsSync(dbPath)) {
165
+ throw new NotFoundError(
166
+ `Trails database not found at "${dbPath}". Run a write operation first to initialize it.`
167
+ );
168
+ }
169
+ const db = new Database(dbPath, { readonly: true });
170
+ db.run('PRAGMA foreign_keys = ON');
171
+ return db;
172
+ };
173
+
174
+ export const ensureSubsystemSchema = (
175
+ db: Database,
176
+ options: EnsureSubsystemSchemaOptions
177
+ ): void => {
178
+ ensureSchemaVersionTable(db);
179
+
180
+ db.transaction(() => {
181
+ const currentVersion = readSubsystemVersion(db, options.subsystem);
182
+ if (currentVersion >= options.version) {
183
+ return;
184
+ }
185
+
186
+ options.migrate(currentVersion);
187
+ writeSubsystemVersion(db, options.subsystem, options.version);
188
+ })();
189
+ };
@@ -0,0 +1,301 @@
1
+ import type { SQLQueryBindings } from 'bun:sqlite';
2
+ import { existsSync } from 'node:fs';
3
+
4
+ import { NotFoundError } from './errors.js';
5
+ import { provision } from './provision.js';
6
+ import { Result } from './result.js';
7
+ import type { TopoPinRecord, TopoSaveRecord } from './internal/topo-saves.js';
8
+ import { getTopoPin } from './internal/topo-saves.js';
9
+ import type {
10
+ TopoStoreExportRecord,
11
+ TopoStoreProvisionRecord,
12
+ TopoStoreRef,
13
+ TopoStoreTrailDetailRecord,
14
+ TopoStoreTrailRecord,
15
+ } from './internal/topo-store-read.js';
16
+ import {
17
+ getTopoStoreExport,
18
+ getTopoStoreProvision,
19
+ getTopoStoreTrail,
20
+ listTopoStorePins,
21
+ listTopoStoreProvisions,
22
+ listTopoStoreSaves,
23
+ listTopoStoreTrails,
24
+ queryTopoStore,
25
+ resolveTopoStoreSave,
26
+ } from './internal/topo-store-read.js';
27
+ import { openReadTrailsDb, resolveTrailsDbPath } from './internal/trails-db.js';
28
+ import type { TrailsDbLocationOptions } from './internal/trails-db.js';
29
+
30
+ export type {
31
+ TopoStoreExportRecord,
32
+ TopoStoreProvisionRecord,
33
+ TopoStoreRef,
34
+ TopoStoreTrailDetailRecord,
35
+ TopoStoreTrailRecord,
36
+ } from './internal/topo-store-read.js';
37
+
38
+ export interface ReadOnlyTopoStore {
39
+ readonly exports: {
40
+ get(ref?: TopoStoreRef): TopoStoreExportRecord | undefined;
41
+ };
42
+ readonly pins: {
43
+ get(name: string): TopoPinRecord | undefined;
44
+ list(): readonly TopoPinRecord[];
45
+ };
46
+ query<TRow extends Record<string, unknown>>(
47
+ sql: string,
48
+ bindings?: readonly SQLQueryBindings[]
49
+ ): readonly TRow[];
50
+ readonly provisions: {
51
+ get(
52
+ id: string,
53
+ options?: { readonly save?: TopoStoreRef }
54
+ ): TopoStoreProvisionRecord | undefined;
55
+ list(options?: {
56
+ readonly save?: TopoStoreRef;
57
+ }): readonly TopoStoreProvisionRecord[];
58
+ };
59
+ readonly saves: {
60
+ get(ref?: TopoStoreRef): TopoSaveRecord | undefined;
61
+ latest(): TopoSaveRecord | undefined;
62
+ list(): readonly TopoSaveRecord[];
63
+ };
64
+ readonly trails: {
65
+ get(
66
+ id: string,
67
+ options?: { readonly save?: TopoStoreRef }
68
+ ): TopoStoreTrailDetailRecord | undefined;
69
+ list(options?: {
70
+ readonly intent?: TopoStoreTrailRecord['intent'];
71
+ readonly save?: TopoStoreRef;
72
+ }): readonly TopoStoreTrailRecord[];
73
+ };
74
+ }
75
+
76
+ export interface MockTopoStoreSeed {
77
+ readonly exports?: readonly TopoStoreExportRecord[];
78
+ readonly pins?: readonly TopoPinRecord[];
79
+ readonly provisions?: readonly TopoStoreProvisionRecord[];
80
+ readonly saves?: readonly TopoSaveRecord[];
81
+ readonly trails?: readonly TopoStoreTrailDetailRecord[];
82
+ }
83
+
84
+ const missingStoreMessage =
85
+ 'No saved topo state found. Populate trails.db first or run a topo-backed surface.';
86
+
87
+ const resolveStoreRootDir = (options?: TrailsDbLocationOptions): string =>
88
+ options?.rootDir ?? process.cwd();
89
+
90
+ const requireReadDb = (
91
+ options?: TrailsDbLocationOptions
92
+ ): ReturnType<typeof openReadTrailsDb> => {
93
+ const dbPath = resolveTrailsDbPath(options);
94
+ if (!existsSync(dbPath)) {
95
+ throw new NotFoundError(missingStoreMessage);
96
+ }
97
+ return openReadTrailsDb(options);
98
+ };
99
+
100
+ const requireSavedTopoState = (
101
+ db: ReturnType<typeof openReadTrailsDb>
102
+ ): void => {
103
+ if (resolveTopoStoreSave(db) === undefined) {
104
+ throw new NotFoundError(missingStoreMessage);
105
+ }
106
+ };
107
+
108
+ const withStoredTopoState = <T>(
109
+ options: TrailsDbLocationOptions | undefined,
110
+ run: (db: ReturnType<typeof openReadTrailsDb>) => T
111
+ ): T => {
112
+ const db = requireReadDb(options);
113
+ try {
114
+ requireSavedTopoState(db);
115
+ return run(db);
116
+ } finally {
117
+ db.close();
118
+ }
119
+ };
120
+
121
+ const createSeedResolver = (seed?: MockTopoStoreSeed) => {
122
+ const saves = [...(seed?.saves ?? [])];
123
+ const pins = [...(seed?.pins ?? [])];
124
+ const trails = [...(seed?.trails ?? [])];
125
+ const provisions = [...(seed?.provisions ?? [])];
126
+ const exports = [...(seed?.exports ?? [])];
127
+
128
+ const resolveSave = (ref?: TopoStoreRef): TopoSaveRecord | undefined => {
129
+ if (ref?.saveId !== undefined) {
130
+ return saves.find((save) => save.id === ref.saveId);
131
+ }
132
+ if (ref?.pin !== undefined) {
133
+ const pin = pins.find((candidate) => candidate.name === ref.pin);
134
+ return pin === undefined
135
+ ? undefined
136
+ : saves.find((save) => save.id === pin.saveId);
137
+ }
138
+ return saves[0];
139
+ };
140
+
141
+ return {
142
+ exports,
143
+ pins,
144
+ provisions,
145
+ resolveSave,
146
+ saves,
147
+ trails,
148
+ };
149
+ };
150
+
151
+ export const createMockTopoStore = (
152
+ seed?: MockTopoStoreSeed
153
+ ): ReadOnlyTopoStore => {
154
+ const resolved = createSeedResolver(seed);
155
+
156
+ return {
157
+ exports: {
158
+ get(ref?: TopoStoreRef) {
159
+ const save = resolved.resolveSave(ref);
160
+ return save === undefined
161
+ ? undefined
162
+ : resolved.exports.find((entry) => entry.save.id === save.id);
163
+ },
164
+ },
165
+ pins: {
166
+ get(name: string) {
167
+ return resolved.pins.find((pin) => pin.name === name);
168
+ },
169
+ list() {
170
+ return resolved.pins;
171
+ },
172
+ },
173
+ provisions: {
174
+ get(id, options) {
175
+ const save = resolved.resolveSave(options?.save);
176
+ return resolved.provisions.find(
177
+ (item) =>
178
+ item.id === id && (save === undefined || item.saveId === save.id)
179
+ );
180
+ },
181
+ list(options) {
182
+ const save = resolved.resolveSave(options?.save);
183
+ return save === undefined
184
+ ? []
185
+ : resolved.provisions.filter((item) => item.saveId === save.id);
186
+ },
187
+ },
188
+ query() {
189
+ throw new NotFoundError(
190
+ 'Mock topoStore.query() is unsupported. Seed typed accessors instead.'
191
+ );
192
+ },
193
+ saves: {
194
+ get(ref?: TopoStoreRef) {
195
+ return resolved.resolveSave(ref);
196
+ },
197
+ latest() {
198
+ return resolved.saves[0];
199
+ },
200
+ list() {
201
+ return resolved.saves;
202
+ },
203
+ },
204
+ trails: {
205
+ get(id, options) {
206
+ const save = resolved.resolveSave(options?.save);
207
+ if (save === undefined) {
208
+ return;
209
+ }
210
+ return resolved.trails.find(
211
+ (trail) => trail.id === id && trail.saveId === save.id
212
+ );
213
+ },
214
+ list(options) {
215
+ const save = resolved.resolveSave(options?.save);
216
+ if (save === undefined) {
217
+ return [];
218
+ }
219
+ return resolved.trails.filter(
220
+ (trail) =>
221
+ trail.saveId === save.id &&
222
+ (options?.intent === undefined || trail.intent === options.intent)
223
+ );
224
+ },
225
+ },
226
+ };
227
+ };
228
+
229
+ export const createTopoStore = (
230
+ options?: TrailsDbLocationOptions
231
+ ): ReadOnlyTopoStore => ({
232
+ exports: {
233
+ get(ref?: TopoStoreRef) {
234
+ return withStoredTopoState(options, (db) => getTopoStoreExport(db, ref));
235
+ },
236
+ },
237
+ pins: {
238
+ get(name: string) {
239
+ return withStoredTopoState(options, (db) => getTopoPin(db, name));
240
+ },
241
+ list() {
242
+ return withStoredTopoState(options, (db) => listTopoStorePins(db));
243
+ },
244
+ },
245
+ provisions: {
246
+ get(id, queryOptions) {
247
+ return withStoredTopoState(options, (db) =>
248
+ getTopoStoreProvision(db, id, queryOptions)
249
+ );
250
+ },
251
+ list(queryOptions) {
252
+ return withStoredTopoState(options, (db) =>
253
+ listTopoStoreProvisions(db, queryOptions)
254
+ );
255
+ },
256
+ },
257
+ query<TRow extends Record<string, unknown>>(
258
+ sql: string,
259
+ bindings?: readonly SQLQueryBindings[]
260
+ ) {
261
+ return withStoredTopoState(options, (db) =>
262
+ queryTopoStore<TRow>(db, sql, bindings)
263
+ );
264
+ },
265
+ saves: {
266
+ get(ref?: TopoStoreRef) {
267
+ return withStoredTopoState(options, (db) =>
268
+ resolveTopoStoreSave(db, ref)
269
+ );
270
+ },
271
+ latest() {
272
+ return withStoredTopoState(options, (db) => resolveTopoStoreSave(db));
273
+ },
274
+ list() {
275
+ return withStoredTopoState(options, (db) => listTopoStoreSaves(db));
276
+ },
277
+ },
278
+ trails: {
279
+ get(id, queryOptions) {
280
+ return withStoredTopoState(options, (db) =>
281
+ getTopoStoreTrail(db, id, queryOptions)
282
+ );
283
+ },
284
+ list(queryOptions) {
285
+ return withStoredTopoState(options, (db) =>
286
+ listTopoStoreTrails(db, queryOptions)
287
+ );
288
+ },
289
+ },
290
+ });
291
+
292
+ export const topoStore = provision('topo.store', {
293
+ create: (svc) =>
294
+ Result.ok(
295
+ createTopoStore({
296
+ rootDir: svc.workspaceRoot ?? svc.cwd ?? resolveStoreRootDir(),
297
+ })
298
+ ),
299
+ description: 'Read-only query access to saved topo state in trails.db',
300
+ mock: () => createMockTopoStore(),
301
+ });
@@ -0,0 +1,63 @@
1
+ import { ValidationError } from './errors.js';
2
+ import { Result } from './result.js';
3
+ import type { Topo } from './topo.js';
4
+ import { validateEstablishedTopo as validateDraftFreeTopo } from './draft.js';
5
+ import type { TopoIssue } from './validate-topo.js';
6
+ import { validateTopo } from './validate-topo.js';
7
+
8
+ const PROJECTION_BLOCKING_RULES = new Set([
9
+ 'cross-cycle',
10
+ 'cross-exists',
11
+ 'no-self-cross',
12
+ 'provision-exists',
13
+ 'signal-origin-exists',
14
+ ]);
15
+
16
+ const keepProjectionBlockingIssues = (
17
+ result: ReturnType<typeof validateTopo>
18
+ ) => {
19
+ if (result.isOk()) {
20
+ return result;
21
+ }
22
+
23
+ const issues = (
24
+ result.error.context as { issues?: readonly TopoIssue[] } | undefined
25
+ )?.issues;
26
+ const remainingIssues = issues?.filter((issue) =>
27
+ PROJECTION_BLOCKING_RULES.has(issue.rule)
28
+ );
29
+
30
+ if (remainingIssues === undefined || remainingIssues.length === 0) {
31
+ return Result.ok();
32
+ }
33
+
34
+ return Result.err(
35
+ new ValidationError(
36
+ `Topo validation failed with ${remainingIssues.length} issue(s)`,
37
+ {
38
+ cause: result.error,
39
+ context: { issues: remainingIssues },
40
+ }
41
+ )
42
+ );
43
+ };
44
+
45
+ /**
46
+ * Validate that a topo is ready for established outputs.
47
+ *
48
+ * Established surfaces still require the authored graph to be structurally
49
+ * valid, and they must also reject any remaining draft state.
50
+ */
51
+ export const validateEstablishedTopo = (topo: Topo) => {
52
+ const structural = keepProjectionBlockingIssues(validateTopo(topo));
53
+ if (structural.isErr()) {
54
+ return structural;
55
+ }
56
+
57
+ const established = validateDraftFreeTopo(topo);
58
+ if (established.isErr()) {
59
+ return established;
60
+ }
61
+
62
+ return Result.ok();
63
+ };
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { ValidationError } from './errors.js';
10
+ import { isDraftId } from './draft.js';
10
11
  import type { AnySignal } from './event.js';
11
12
  import { Result } from './result.js';
12
13
  import type { Topo } from './topo.js';
@@ -100,7 +101,7 @@ const checkCrosses = (
100
101
  rule: 'no-self-cross',
101
102
  trailId: id,
102
103
  });
103
- } else if (!topo.has(crossedId)) {
104
+ } else if (!topo.has(crossedId) && !isDraftId(crossedId)) {
104
105
  issues.push({
105
106
  message: `Crosses "${crossedId}" which is not in the topo`,
106
107
  rule: 'cross-exists',
@@ -121,7 +122,10 @@ const checkProvisions = (
121
122
 
122
123
  for (const [id, trail] of trails) {
123
124
  for (const declaredProvision of trail.provisions) {
124
- if (!topo.hasProvision(declaredProvision.id)) {
125
+ if (
126
+ !topo.hasProvision(declaredProvision.id) &&
127
+ !isDraftId(declaredProvision.id)
128
+ ) {
125
129
  issues.push({
126
130
  message: `Provision "${declaredProvision.id}" is not in the topo`,
127
131
  rule: 'provision-exists',
@@ -187,7 +191,7 @@ const checkSignalOrigins = (
187
191
  continue;
188
192
  }
189
193
  for (const originId of evt.from) {
190
- if (!topo.has(originId)) {
194
+ if (!topo.has(originId) && !isDraftId(originId)) {
191
195
  issues.push({
192
196
  message: `Signal origin "${originId}" is not in the topo`,
193
197
  rule: 'signal-origin-exists',
@@ -1 +1 @@
1
- {"root":["./src/blob-ref.ts","./src/branded.ts","./src/collections.ts","./src/context.ts","./src/derive.ts","./src/errors.ts","./src/event.ts","./src/execute.ts","./src/fetch.ts","./src/gate.ts","./src/guards.ts","./src/index.ts","./src/path-security.ts","./src/provision-config.ts","./src/provision.ts","./src/resilience.ts","./src/result.ts","./src/run.ts","./src/serialization.ts","./src/signal.ts","./src/topo.ts","./src/trail.ts","./src/type-utils.ts","./src/types.ts","./src/validate-topo.ts","./src/validation.ts","./src/workspace.ts","./src/patterns/bulk.ts","./src/patterns/change.ts","./src/patterns/date-range.ts","./src/patterns/index.ts","./src/patterns/pagination.ts","./src/patterns/progress.ts","./src/patterns/sorting.ts","./src/patterns/status.ts","./src/patterns/timestamps.ts","./src/redaction/index.ts","./src/redaction/patterns.ts","./src/redaction/redactor.ts"],"version":"5.9.3"}
1
+ {"root":["./src/blob-ref.ts","./src/branded.ts","./src/collections.ts","./src/context.ts","./src/derive.ts","./src/draft.ts","./src/errors.ts","./src/event.ts","./src/execute.ts","./src/fetch.ts","./src/gate.ts","./src/guards.ts","./src/index.ts","./src/path-security.ts","./src/provision-config.ts","./src/provision.ts","./src/resilience.ts","./src/result.ts","./src/run.ts","./src/serialization.ts","./src/signal.ts","./src/topo-store.ts","./src/topo.ts","./src/trail.ts","./src/type-utils.ts","./src/types.ts","./src/validate-established-topo.ts","./src/validate-topo.ts","./src/validation.ts","./src/workspace.ts","./src/internal/topo-saves.ts","./src/internal/topo-store-read.ts","./src/internal/topo-store.ts","./src/internal/trails-db.ts","./src/patterns/bulk.ts","./src/patterns/change.ts","./src/patterns/date-range.ts","./src/patterns/index.ts","./src/patterns/pagination.ts","./src/patterns/progress.ts","./src/patterns/sorting.ts","./src/patterns/status.ts","./src/patterns/timestamps.ts","./src/redaction/index.ts","./src/redaction/patterns.ts","./src/redaction/redactor.ts"],"version":"5.9.3"}