Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Seed the demo: the product working on first boot - #75

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data
Sep 1, 2026
Merged

Seed the demo: the product working on first boot#75
os-warren merged 2 commits into
mainfrom
claude/issue-7-seed-data

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7

pnpm dev on an empty database now opens on a system that is obviously working: a three-level business-unit tree, thirteen people, a twenty-item role catalog across three position codes, thirty-one duties and 186 tasks covering six months of history plus everything currently in flight. 459 rows, errored: 0.

The two decisions worth reviewing

History comes out of the dispatcher's own planner, not a second period walk.demo-history.ts calls planDispatch twice — once with a six-month backfill window, once as today's scheduled run — and unions the drafts on (duty, owner, period_key). The card's hardest rule (period keys must come from the engine, never hand-typed) is then satisfied structurally rather than by discipline, and two invariants fall out of the shape instead of being asserted on top of it:

  • Standing duties hold zero tasks because planForDuty refuses them by form before it reads anything else. SKIPS is exported so the test asserts the mechanism, not just that no such row happens to exist.
  • Paused duties hold zero tasks the same way (not_active).

The seeded rows are literally what the dispatcher would have produced — same keys, same due dates, same lead windows — so a real dispatch run over these duties reproduces them and the unique index absorbs it.

Four task datasets, and the last two are the half that fails silently. Per #32 / #64: completed_at rides along on a system-context insert, last_update_at cannot — beforeInsert stamps it unconditionally and lifecycle hooks run on the seed path. It takes a second pass in mode: 'update' carrying only that column. Skip it and every row reads as touched at boot: "Not moving" is empty, and the seed reports complete success.

The two insert datasets use different external ids, deliberately: dispatched rows are keyed (duty, owner, period_key) — the dispatch identity itself — because a subject repeats across every period of its duty; assignment fan-out rows are keyed (subject, owner) because they have no period_key at all, and a composite containing a blank collapses to an empty key that matches nothing and re-inserts on every boot.

What the screens show

Measured through the running app's REST API as the logged-in dev admin, not inferred from the fixture:

ScreenShows
My week5 — two monthlies, a quarterly in progress, the half-year audit, one self-declared
Late4 — past due, still open, across four people and three units
Not moving3 — and 2 of them are not yet due, which is the product's whole argument
Board5 populated columns: 151 done · 27 open · 6 in progress · 1 skipped · 1 cancelled
Schedule (gantt)186 bars, every row has both visible_from and due_date
Calendar186
Role catalog20 items, 3 position codes, 19 citing a clause
By business unit86 / 61 / 31 / 7 / 1 — five units, all different
Manager dashboard (#67)open 29 · >7d 6 · >14d 3 · >30d 2 (properly nested), unit bar chart, 27 tasks in the forward workload window
Assignments → Sent by me1
Work log8 of 15, mostly private

Every {current_user_id} view is scoped to Dev Admin, the account objectstack dev logs you in as — a demo whose data belongs entirely to twelve other people renders four screens blank. That row is seeded carrying its natural key and nothing else, so on a real boot the loader's no-op-replay check skips it rather than updating a live credential-bearing account. Verified: email and sys_account untouched, 13 users not 14.

⚠️ One thing to know before you boot — objectstack#14157

On a genuinely fresh.objectstack/data, this app now has no loginable account. plugin-auth seeds the dev admin only on a zero-user DB and does it on kernel:ready, which fires after the app seed — so thirteen seeded people pre-empt it, permanently. sign-in 401s and bootstrap-status says hasOwner: true, so the console offers no first-admin flow either.

It is a platform gate that asks the wrong question (any sys_user row, rather than any credential-bearing account) and it is not fixable from src/data/. Filed upstream as objectstack-ai/objectstack#14157 with both measurements, and tracked here as #73. If you have run pnpm dev in a worktree before, the admin is already there and everything just works — that is the path this was verified on, end to end.

Verification

All four gates green at e671ea1, run after the final commit: pnpm validate exit 0 (one expected warning — the hierarchy-security provider, which AGENTS.md documents as this repo's expected state) · pnpm typecheck exit 0 · pnpm test exit 0, Test Files 20 passed (20) / Tests 536 passed (536) · pnpm build exit 0, Artifact: dist/objectstack.json (276.0 KB).

test/seed.test.ts (25 tests) boots a real kernel with skipSeedData: false and reads the rows back from the database — the failures this card exists to prevent (an owner natural key that resolved to nothing, a stripped completed_at, a last_update_at overwritten at boot) all report success on the fixture side.

Ablation. Removed both mode: 'update' datasets from the barrel; predicted the stagnation assertions go red and nothing else. Confirmed on disk before running by the injected marker plus git diff --stat (1 insertion, 2 deletions), restored by a trap … EXIT INT TERM with the restore verified as an empty git diff HEAD. Observed exactly the prediction: Tests 3 failed | 22 passed (25) — "Not moving has 2-3 rows", "stagnation is NOT lateness", and "the replay leaves the stalled rows stalled". The other 22 stayed green, which is the point: without that pass the seed still loads perfectly and only the product's headline signal goes quiet.

Idempotence is exercised through the platform's own loader with the boot's own config, asserting totalInserted === 0andtotalRecords === every declared row, so "inserted nothing" cannot be an empty run.

Also filed

Scope

src/data/** and test/seed.test.ts only. No change to objectstack.config.ts, src/objects/, src/hooks/, src/jobs/, src/dashboards/, src/flows/ or AGENTS.md. Rebased twice mid-flight, onto #66 (cadence fields are now form-scoped — the two standing catalog items carry none of the five) and onto #72 (record_change is bound now; a seeded assignment still does not fan out, because the loader's own skipTriggers exempts it, which is a better reason and a stable one).

No real company, person or regulation is named anywhere: the domain is under RFC 2606's reserved .example, and every citation is an invented internal policy number. Nothing in the seed ranks or compares item counts between people.

Generated by Claude Code


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 08:55
`pnpm dev` on an empty database now opens on a running system rather than
five empty grids — a three-level business-unit tree, thirteen people, a
twenty-item role catalog across three position codes, thirty-one duties and
six months of dispatched history.
History is produced by the dispatcher's own planner (`planDispatch`) rather
than by a second period walk, so every period key is the engine's spelling by
construction and "standing duties hold zero tasks" is structurally impossible
to violate rather than merely absent from the fixture.
`last_update_at` is written by a second `mode: 'update'` seed pass, per #32 /
PR #64 — an insert can never carry it, and without that pass the "Not moving"
view is empty while the seed reports success.
Fixes#7
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Keeps every non-designated row inside the fortnight while putting real
values in the 7-to-14-day band, so the dashboard's nested >7d / >14d / >30d
tiles read 6 / 3 / 2 rather than 3 / 3 / 2.
Also corrects the fan-out comment after #72: the reason a seeded assignment
does not fan out is the loader's own skipTriggers, not an unbound trigger.
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo seed data — the product working on first boot

1 participant

@os-warren