dreamteamer 0.25.1 → 0.27.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/bin/dreamteamer.js +28 -0
- package/collections/collections.collection.yaml +13 -4
- package/collections/repos.collection.yaml +6 -0
- package/package.json +1 -1
- package/skills/using-dreamteamer/SKILL.md +16 -0
- package/src/cli.js +14 -0
- package/src/collections-cli.js +14 -6
- package/src/harnesses.js +32 -11
- package/src/presentation.js +23 -4
- package/src/schema-ops.js +3 -0
package/bin/dreamteamer.js
CHANGED
|
@@ -12,6 +12,34 @@ import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
|
12
12
|
const MODULE_SEGMENTS = new Set(['node_modules', 'git_modules', 'modules']);
|
|
13
13
|
|
|
14
14
|
const self = fileURLToPath(import.meta.url);
|
|
15
|
+
|
|
16
|
+
// `--vault <path>` — operate on ANOTHER workspace without leaving this one. Consumed HERE, before
|
|
17
|
+
// anything else resolves, because every verb downstream answers "which workspace am I in" from
|
|
18
|
+
// process.cwd(): changing the directory once IS the whole implementation, and it also points the
|
|
19
|
+
// dev-clone walk below at the TARGET's git_modules rather than the caller's.
|
|
20
|
+
//
|
|
21
|
+
// ⚠ It is spliced out of process.argv, not just out of the slice handed to run(): the dev-clone
|
|
22
|
+
// branch re-enters this same file, and a flag left in place would be applied a SECOND time — the
|
|
23
|
+
// path resolving against the already-changed directory, i.e. at the wrong workspace, silently.
|
|
24
|
+
//
|
|
25
|
+
// The path resolves against the INVOKING directory, which is where the caller typed it. Every other
|
|
26
|
+
// relative path in the command then resolves against the target — the same rule a `cd` would give.
|
|
27
|
+
const vaultAt = process.argv.indexOf('--vault', 2);
|
|
28
|
+
if (vaultAt !== -1) {
|
|
29
|
+
const target = process.argv[vaultAt + 1];
|
|
30
|
+
if (!target || target.startsWith('-')) {
|
|
31
|
+
console.error('\u2716 --vault needs a path to a workspace, e.g. `--vault ../another-workspace`');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const resolved = path.resolve(process.cwd(), target);
|
|
35
|
+
if (!fs.existsSync(resolved)) {
|
|
36
|
+
console.error(`\u2716 --vault ${target} \u2014 no such directory (resolved to ${resolved})`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
process.chdir(resolved);
|
|
40
|
+
process.argv.splice(vaultAt, 2);
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
const devBin = findDevClone(process.cwd());
|
|
16
44
|
// `realpath`, NOT `path.resolve`: resolve is pure string math and does not follow symlinks, so a
|
|
17
45
|
// workspace whose git_modules/dreamteamer is a SYMLINK to the engine it is already running (the
|
|
@@ -172,10 +172,19 @@ schema:
|
|
|
172
172
|
group:
|
|
173
173
|
type: string
|
|
174
174
|
description: >-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
The collection's PARTITION — which family of nouns it belongs to, authored freely (`crm`,
|
|
176
|
+
`finance`, `family`, `content`…) and set with `dt set collections/<c> group=<g>`. One value
|
|
177
|
+
is reserved and load-bearing: `group: system` says the collection is the workspace's own
|
|
178
|
+
MACHINERY rather than one of its domain nouns, so it is left out of the generated block's
|
|
179
|
+
domain listing, named on the system-collections line instead, and drawn on a surface's
|
|
180
|
+
schema surface rather than in the record tree. ⚠ It does NOT change where records live or
|
|
181
|
+
whether they can be written — that is `storage.base`, asked separately, and `repos` is the
|
|
182
|
+
collection where the two answers split: `group: system` and workspace-stored records the
|
|
183
|
+
operator edits by hand. Nearly removed on 2026-08-11, when the nav stopped grouping by it in
|
|
184
|
+
favour of `owner` (a module, which has a title of its own) and nothing else read it; kept
|
|
185
|
+
then so the change would be a code revert rather than a data migration, and because a
|
|
186
|
+
workspace may want a partition that deliberately DIFFERS from its modules. That is what it
|
|
187
|
+
became.
|
|
179
188
|
order: 10
|
|
180
189
|
list_fields: [name, last-modified]
|
|
181
190
|
icon: schema
|
|
@@ -52,4 +52,10 @@ schema:
|
|
|
52
52
|
order: 145
|
|
53
53
|
list_fields: [name, identity, ref, url]
|
|
54
54
|
icon: source
|
|
55
|
+
# MACHINERY, NOT A DOMAIN NOUN, and `group: system` is the whole statement of it. `repos` is the
|
|
56
|
+
# engine's only collection whose records live in `data/`, which used to make every workspace render
|
|
57
|
+
# a **System** module group with one collection under it — the group the block's own renderer says
|
|
58
|
+
# should not exist (harnesses.js). Being in the `system` partition folds it out of the block's
|
|
59
|
+
# domain listing and onto the schema surface instead. The records stay real, writable and committed;
|
|
60
|
+
# only the presentation changes.
|
|
55
61
|
group: system
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dreamteamer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "A workspace compiler for coding agents — schema-validated records as plain files over git, compiled into every harness",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Gilad Khen <giladkhen@gmail.com>",
|
|
@@ -71,6 +71,22 @@ on: **validation is hard** (unknown fields included; an invalid write is rejecte
|
|
|
71
71
|
with no partial state), and **a write does not commit** — `dt commit` publishes, scoped
|
|
72
72
|
(`references/records.md`).
|
|
73
73
|
|
|
74
|
+
## one session, several workspaces
|
|
75
|
+
|
|
76
|
+
a harness can be pointed at more than one workspace at a time. when it is, it loads skills, commands
|
|
77
|
+
and agents from ALL of them and the workspace INSTRUCTIONS from the PRIMARY one only — measured:
|
|
78
|
+
zero instruction blocks from the others across every session on record. so the schema in context is
|
|
79
|
+
the primary workspace's, whoever shipped the skill you are reading.
|
|
80
|
+
|
|
81
|
+
- **start a session in the workspace it is about.** it is the only thing that loads that workspace's
|
|
82
|
+
instructions, and it settles name collisions in its favour.
|
|
83
|
+
- **to touch another workspace from here, `--vault <path>` — not `cd`.** the flag resolves the
|
|
84
|
+
target's schema, validates and commits there, and leaves the working directory alone; a `cd`
|
|
85
|
+
outlives the command.
|
|
86
|
+
- **every record write prints the workspace it landed in** — `✔ <workspace> · <path>`. a write to
|
|
87
|
+
the wrong workspace passes validation, because the collection usually exists in both, so that line
|
|
88
|
+
is what distinguishes them.
|
|
89
|
+
|
|
74
90
|
## two acts, one map
|
|
75
91
|
|
|
76
92
|
Act one is **working with data** — the records themselves. Act two is **modeling the workspace**
|
package/src/cli.js
CHANGED
|
@@ -35,6 +35,10 @@ const QUIET = ['ignore', 'pipe', 'ignore'];
|
|
|
35
35
|
|
|
36
36
|
export const USAGE = `usage: dreamteamer <verb> [<target>] [flags]
|
|
37
37
|
|
|
38
|
+
--vault <path> run against another workspace. The path resolves from the directory you typed it
|
|
39
|
+
in; every other path in the command resolves inside the target. The working
|
|
40
|
+
directory is unchanged.
|
|
41
|
+
|
|
38
42
|
record verbs (hard validation — invalid writes are rejected before disk).
|
|
39
43
|
A <target> is either a collection name or a <collection>/<id> reference; the reference splits at
|
|
40
44
|
the longest DECLARED collection prefix, so finance/transactions/2026/03/coffee is ONE argument:
|
|
@@ -269,6 +273,16 @@ const FIELD_VERBS = ['add-field', 'set-field', 'rm-field', 'rename-field'];
|
|
|
269
273
|
// misspelling — `dt commit --dryrun` COMMITTED, because `rest.includes('--dry-run')` is false for a
|
|
270
274
|
// flag nobody typed correctly. The record/system/field verbs are checked in `collections-cli.js`,
|
|
271
275
|
// beside the parser they share; these nine have no shared parser, so the table is here.
|
|
276
|
+
// Flags consumed BEFORE dispatch, in bin/dreamteamer.js, so no verb ever sees them. They are real
|
|
277
|
+
// and honoured; they are simply honoured earlier than VERB_FLAGS/WORKSPACE_FLAGS can express.
|
|
278
|
+
//
|
|
279
|
+
// ⚠ Listing one here EXEMPTS it from the "documented flags are accepted by some verb" guard, which
|
|
280
|
+
// exists because 0.19.0 shipped a flag that was documented and silently swallowed. The exemption is
|
|
281
|
+
// only safe while the behaviour is pinned somewhere else: `--vault` is covered end-to-end in
|
|
282
|
+
// test/integration/cross-workspace-targeting.test.js. A global flag with no such file is the exact
|
|
283
|
+
// defect that guard was written to catch \u2014 add the coverage, not the entry.
|
|
284
|
+
export const GLOBAL_FLAGS = ['vault'];
|
|
285
|
+
|
|
272
286
|
export const WORKSPACE_FLAGS = {
|
|
273
287
|
init: ['name', 'data-path', 'harnesses', 'workspace-module'], update: [],
|
|
274
288
|
install: ['clone', 'dry-run', 'json', 'link-env', 'all', 'hook', 'print-adapters'],
|
package/src/collections-cli.js
CHANGED
|
@@ -118,6 +118,14 @@ export function collectionCommand(ws, collection, verb, args) {
|
|
|
118
118
|
|
|
119
119
|
const d = store.descriptor(collection);
|
|
120
120
|
|
|
121
|
+
// Every record write says WHICH workspace it landed in. `--vault` is OPTIONAL and a bare command
|
|
122
|
+
// resolves from the working directory, so the one thing the flag cannot do is tell you when you
|
|
123
|
+
// forgot it \u2014 this can, for the price of one word, with no state and nothing refused. A write
|
|
124
|
+
// aimed at one workspace and landed in another becomes visible on the very next line of output
|
|
125
|
+
// instead of never: on a machine holding several workspaces most collection NAMES are shared while
|
|
126
|
+
// their fields are not, so the misplaced write succeeds and reports success.
|
|
127
|
+
const at = ws.pkg?.name ? `${ws.pkg.name} \u00b7 ` : '';
|
|
128
|
+
|
|
121
129
|
switch (verb) {
|
|
122
130
|
case 'list': {
|
|
123
131
|
const { rows, narrowed } = narrowRows(store, d, collection, flags);
|
|
@@ -167,7 +175,7 @@ export function collectionCommand(ws, collection, verb, args) {
|
|
|
167
175
|
const id = need(pos, 0, 'id');
|
|
168
176
|
if (!flags.from) throw new Error(`"${collection}" is a \`codec: file\` collection — pass --from <path> with the file to import`);
|
|
169
177
|
const { id: written, file } = store.addFile(collection, id, flags.from, { force: !!flags.force });
|
|
170
|
-
flags.json ? emit(JSON.stringify({ id: written, path: rel(ws.root, file) })) : console.log(`✔ ${rel(ws.root, file)}`);
|
|
178
|
+
flags.json ? emit(JSON.stringify({ id: written, path: rel(ws.root, file) })) : console.log(`✔ ${at}${rel(ws.root, file)}`);
|
|
171
179
|
return 0;
|
|
172
180
|
}
|
|
173
181
|
if (flags.from) throw new Error(`--from imports a file as a record, and "${collection}" is not a \`codec: file\` collection`);
|
|
@@ -175,7 +183,7 @@ export function collectionCommand(ws, collection, verb, args) {
|
|
|
175
183
|
const { id, file, idFallback } = store.add(collection, fields, { id: flags.id });
|
|
176
184
|
flags.json
|
|
177
185
|
? emit(JSON.stringify({ id, path: rel(ws.root, file), ...(idFallback ? { idFallback } : {}) }))
|
|
178
|
-
: console.log(`✔ ${rel(ws.root, file)}`);
|
|
186
|
+
: console.log(`✔ ${at}${rel(ws.root, file)}`);
|
|
179
187
|
// ⚠ SAY IT, EVERY TIME. The id was derived from a hash because the value it is generated
|
|
180
188
|
// from carries no a-z0-9 — the write succeeded and the record is fine, but the id is
|
|
181
189
|
// unreadable and unguessable, and nobody finds that out until they try to type it. By
|
|
@@ -194,7 +202,7 @@ export function collectionCommand(ws, collection, verb, args) {
|
|
|
194
202
|
Object.assign(changes, coerceArrays(d, stripMeta(flags)));
|
|
195
203
|
if (!Object.keys(changes).length) throw new Error('nothing to set — pass key=value pairs or --key value flags');
|
|
196
204
|
store.set(collection, id, changes);
|
|
197
|
-
flags.json ? emit(JSON.stringify({ id })) : console.log(
|
|
205
|
+
flags.json ? emit(JSON.stringify({ id })) : console.log(`✔ ${at}updated`);
|
|
198
206
|
return 0;
|
|
199
207
|
}
|
|
200
208
|
// Manual ordering. ONE record is written per move — that is the entire feature; a dense
|
|
@@ -228,20 +236,20 @@ export function collectionCommand(ws, collection, verb, args) {
|
|
|
228
236
|
|
|
229
237
|
const id = need(pos, 0, 'id');
|
|
230
238
|
store.set(collection, id, { [field]: placementKey(rows, id, flags, collection) });
|
|
231
|
-
flags.json ? emit(JSON.stringify({ id })) : console.log(
|
|
239
|
+
flags.json ? emit(JSON.stringify({ id })) : console.log(`✔ ${at}moved`);
|
|
232
240
|
return 0;
|
|
233
241
|
}
|
|
234
242
|
case 'rm': {
|
|
235
243
|
const id = need(pos, 0, 'id');
|
|
236
244
|
if (flags['dry-run']) return dryRunPlan(`rm ${collection}/${id}`, { records: store.ids(collection).has(id) ? 1 : 0, refs: store.findInboundRefs(`${collection}/${id}`).length });
|
|
237
245
|
const { inboundIgnored } = store.rm(collection, id, { force: !!flags.force });
|
|
238
|
-
flags.json ? emit(JSON.stringify({ id, removed: true, inboundIgnored })) : console.log(`✔ removed${inboundIgnored ? ` (${inboundIgnored} inbound reference(s) left dangling — run \`dreamteamer check\`)` : ''}`);
|
|
246
|
+
flags.json ? emit(JSON.stringify({ id, removed: true, inboundIgnored })) : console.log(`✔ ${at}removed${inboundIgnored ? ` (${inboundIgnored} inbound reference(s) left dangling — run \`dreamteamer check\`)` : ''}`);
|
|
239
247
|
return 0;
|
|
240
248
|
}
|
|
241
249
|
case 'rename': {
|
|
242
250
|
const out = store.rename(collection, need(pos, 0, 'old id'), need(pos, 1, 'new id'));
|
|
243
251
|
if (flags.json) { emit(JSON.stringify(out)); return 0; }
|
|
244
|
-
console.log(`✔ renamed ${collection}/${need(pos, 0, 'old id')} → ${collection}/${out.id}`);
|
|
252
|
+
console.log(`✔ ${at}renamed ${collection}/${need(pos, 0, 'old id')} → ${collection}/${out.id}`);
|
|
245
253
|
if (out.touched) console.log(`✔ rewrote ${out.rewrites} inbound reference(s) across ${out.touched} file(s)`);
|
|
246
254
|
return 0;
|
|
247
255
|
}
|
package/src/harnesses.js
CHANGED
|
@@ -142,7 +142,16 @@ const NOTEBOOK_PLANS = [
|
|
|
142
142
|
function notebooklmBlock(entries, version) {
|
|
143
143
|
const index = buildCollectionsIndex(entries);
|
|
144
144
|
const modules = buildModulesIndex(entries);
|
|
145
|
-
|
|
145
|
+
// ⚠ `generated`, NOT `systemGroup` — the same split the orientation block's system line makes,
|
|
146
|
+
// asked for the opposite reason. There the question is presentational ("group it out of the
|
|
147
|
+
// domain listing"); HERE it is a STORAGE question, because every number and name below has to
|
|
148
|
+
// describe what `dt export notebooklm` will actually ship, and the export ships by storage
|
|
149
|
+
// (`storage.base !== 'runtime'` — export-notebooklm.js). Reading the partition here made this file
|
|
150
|
+
// say "exports 1 collections" and list only `notes` for a workspace whose export shipped
|
|
151
|
+
// `repos.md` as a second source and headed a `## module: System` group for it in the schema map
|
|
152
|
+
// — so the persona generated from this line would not know about a source it had been given.
|
|
153
|
+
// The count and the export are pinned to each other by a test (notebooklm-harness.test.js).
|
|
154
|
+
const data = index.filter((c) => !c.generated);
|
|
146
155
|
const withheldCollections = data.filter((c) => c.sensitive).map((c) => c.name);
|
|
147
156
|
const withheldFields = data.flatMap((c) => c.sensitiveFields.map((f) => `${c.name}.${f}`));
|
|
148
157
|
const exported = data.filter((c) => !c.sensitive);
|
|
@@ -245,10 +254,16 @@ function buildCollectionsIndex(entries) {
|
|
|
245
254
|
try { d = load(e.bytes.toString('utf8')) ?? {}; } catch { /* unparseable descriptor */ }
|
|
246
255
|
index.push({
|
|
247
256
|
name: d.name ?? m[1],
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
257
|
+
// ⚠ TWO QUESTIONS, KEPT SEPARATE. `systemGroup` answers "is this machinery, group it out
|
|
258
|
+
// of the domain listing"; `generated` answers "is this build output the hand-edit warning
|
|
259
|
+
// is about" — they agreed until `repos`, machinery whose records are still real files
|
|
260
|
+
// under `data/`. The partition is AUTHORED (`group: system` on the descriptor, ten core
|
|
261
|
+
// collections carry it), never derived from storage: a workspace collection may be its
|
|
262
|
+
// own apparatus, and a runtime-stored one may not be. An unparseable descriptor (`d = {}`
|
|
263
|
+
// from the catch above) carries neither key and answers false to both, which leaves it in
|
|
264
|
+
// the domain listing — the visible failure rather than the silent one.
|
|
265
|
+
systemGroup: d.group === 'system',
|
|
266
|
+
generated: d.storage?.base === 'runtime',
|
|
252
267
|
description: flat(d.description),
|
|
253
268
|
useWhen: flat(d.use_when),
|
|
254
269
|
module: d.module ?? '',
|
|
@@ -354,10 +369,10 @@ function collectionsSection(index, modules, workspaceModule) {
|
|
|
354
369
|
'collection\'s `write:` line names only what the store REFUSES — required fields that have no',
|
|
355
370
|
'default (a defaulted one is filled in for you), closed enums with their size, one example.',
|
|
356
371
|
];
|
|
357
|
-
const data = index.filter((c) => !c.
|
|
372
|
+
const data = index.filter((c) => !c.systemGroup);
|
|
358
373
|
const isWs = (m) => m.path === `modules/${workspaceModule}/`;
|
|
359
374
|
const groups = modules
|
|
360
|
-
.filter((m) => { const own = index.filter((c) => c.module === m.id); return own.some((c) => !c.
|
|
375
|
+
.filter((m) => { const own = index.filter((c) => c.module === m.id); return own.some((c) => !c.systemGroup) || (!own.length && (m.skills.length || m.commands.length || m.bin.length || m.proofs.length)); })
|
|
361
376
|
.sort((a, b) => (isWs(b) - isWs(a)) || a.title.localeCompare(b.title));
|
|
362
377
|
for (const m of groups) {
|
|
363
378
|
const where = [`\`${m.id}\``, m.path ? m.path.replace(/\/$/, '') : 'the workspace root', ...(m.namespaces.length ? [`namespaces: ${m.namespaces.join(' · ')}`] : [])];
|
|
@@ -370,13 +385,19 @@ function collectionsSection(index, modules, workspaceModule) {
|
|
|
370
385
|
if (c.write) lines.push(` write: ${c.write}`);
|
|
371
386
|
}
|
|
372
387
|
}
|
|
373
|
-
const
|
|
388
|
+
const sys = index.filter((c) => c.systemGroup);
|
|
389
|
+
const system = sys.filter((c) => c.generated).map((c) => c.name);
|
|
390
|
+
const kept = sys.filter((c) => !c.generated).map((c) => c.name);
|
|
374
391
|
// ⚠ THIS LINE IS THE FIRST THING A SESSION READS about the system collections, and until 0.19.0
|
|
375
392
|
// it said "schema-ops only", which named an internal module and a grammar that no longer exists.
|
|
376
393
|
// It now names the VERBS and the one policy difference, because an agent that knows the verbs
|
|
377
|
-
// exist still has to be told that these commit and records do not.
|
|
378
|
-
|
|
379
|
-
|
|
394
|
+
// exist still has to be told that these commit and records do not. And it is PARTITIONED on
|
|
395
|
+
// `generated`, not `systemGroup`: the partition only answers the grouping question, so a system
|
|
396
|
+
// collection whose records are real files (`repos`) gets its own trailing clause instead of being
|
|
397
|
+
// told "it is build output" — a sentence that was false of it and is false of the next data-backed
|
|
398
|
+
// system collection too, since the split is derived rather than naming one.
|
|
399
|
+
if (sys.length) {
|
|
400
|
+
lines.push('', `- system collections — the SAME verbs (add · set · rm · rename · list · get), plus \`dt add-field\`/\`set-field\`/\`rm-field\`/\`rename-field <collection>\`. A system write COMMITS ITSELF, in the repo holding the source; a record write does not (\`dt commit\` publishes).${system.length ? ` Never hand-edit \`.dreamteamer/\` — it is build output: ${system.join(' · ')}.` : ''}${kept.length ? ` Machinery whose records are real files you edit like any other: ${kept.join(' · ')}` : ''}`);
|
|
380
401
|
}
|
|
381
402
|
return lines;
|
|
382
403
|
}
|
package/src/presentation.js
CHANGED
|
@@ -82,6 +82,10 @@ function collectionRow(d) {
|
|
|
82
82
|
// it — the extension reads the presentation contract, not raw descriptors.
|
|
83
83
|
if (typeof d.sort_field === 'string') meta.sort_field = d.sort_field;
|
|
84
84
|
|
|
85
|
+
// The collection's PARTITION, and the reserved value `system` is how a surface knows to draw this
|
|
86
|
+
// collection as the workspace's machinery rather than in the record tree. It is carried in `meta`
|
|
87
|
+
// beside the other presentation keys because that is the question it answers; the top-level
|
|
88
|
+
// `system` below answers a different one and they must never be folded together.
|
|
85
89
|
if (typeof d.group === 'string') meta.group = d.group;
|
|
86
90
|
if (typeof d.description === 'string' && d.description.length > 0) meta.description = d.description;
|
|
87
91
|
// ⚠ `system` IS NOT `readonly`, AND SAYING SO COST A RELEASE. Until 0.19.0 the two were the
|
|
@@ -107,10 +111,25 @@ function collectionRow(d) {
|
|
|
107
111
|
// already answers with its own sentence naming the fix, which is the contract the schema
|
|
108
112
|
// surfaces are built on.
|
|
109
113
|
//
|
|
110
|
-
// `system` STAYS and is unchanged: the CLI and REST dispatch key on it
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
+
// `system` STAYS and is unchanged: the CLI and REST dispatch key on it. A consumer that disables
|
|
115
|
+
// editing must key on `meta.readonly` (per field, as `id`, `last-modified` and relation mirrors
|
|
116
|
+
// do) or on the verb it is about to offer — never on `system`.
|
|
117
|
+
//
|
|
118
|
+
// ⚠ AND `system` IS NO LONGER WHAT PUTS A KIND ON THE SCHEMA SURFACE — that clause stood here
|
|
119
|
+
// until `repos` and belongs to `meta.group` now. TWO QUESTIONS, KEPT SEPARATE, exactly as
|
|
120
|
+
// `buildCollectionsIndex` in src/harnesses.js keeps them:
|
|
121
|
+
//
|
|
122
|
+
// `system` decides HOW A WRITE IS PERFORMED. Is this collection's storage the compiled
|
|
123
|
+
// runtime? The CLI and the REST layer dispatch on it, and a surface that gets
|
|
124
|
+
// it wrong answers 400 to a write the store would have accepted.
|
|
125
|
+
// `meta.group` decides WHERE THE COLLECTION IS DRAWN. Its reserved value `system` says this
|
|
126
|
+
// is the workspace's machinery rather than one of its domain nouns, and folds
|
|
127
|
+
// the collection out of the record tree and onto the schema surface.
|
|
128
|
+
//
|
|
129
|
+
// The two agreed for every collection until `repos` — machinery whose records are ordinary,
|
|
130
|
+
// hand-edited files under `data/` — and it is why they must never be collapsed into one: a
|
|
131
|
+
// surface routing a `repos` write by the partition sends it to the system write path, whose
|
|
132
|
+
// entity ops know no such kind, and the write dies as a 400. Measured.
|
|
114
133
|
const system = d.storage?.base === 'runtime';
|
|
115
134
|
return { collection: d.name, meta, system };
|
|
116
135
|
}
|
package/src/schema-ops.js
CHANGED
|
@@ -755,6 +755,9 @@ const COLLECTION_SETTABLE = {
|
|
|
755
755
|
title: (v) => String(v),
|
|
756
756
|
title_template: (v) => String(v),
|
|
757
757
|
icon: (v) => String(v),
|
|
758
|
+
// The collection's partition. `group=system` is the reserved value: it moves the collection out
|
|
759
|
+
// of the block's domain listing and onto a surface's schema surface, and changes nothing about
|
|
760
|
+
// where its records live or whether they can be written.
|
|
758
761
|
group: (v) => String(v),
|
|
759
762
|
sort_field: (v) => String(v),
|
|
760
763
|
order: (v) => {
|