Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

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

Refuse a recurring catalog item with no frequency - #81

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules
Sep 1, 2026
Merged

Refuse a recurring catalog item with no frequency#81
os-warren merged 2 commits into
mainfrom
claude/issue-65-catalog-item-cadence-rules

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#65

duly_duty has always carried recurring_needs_frequency. duly_catalog_item never did: #61 mirrored the three standing / non-recurring cadence rules onto it and left the converse direction open. This adds the missing rule, in duly_duty's wording verbatim — the same convention the three existing mirrored rules already use, and what lets both objects be asserted against one message constant.

Metadata only. No handler, no hook: src/objects/catalog-item.object.ts gains one validations[] entry.

What the gap actually was

The issue's premise holds, and two details of it turned out differently under measurement (@objectstack/runtime 17.2.0, booted in-memory kernel — not read off the source):

  • Only UPDATE can reach it.applyFieldDefaults runs on INSERT only, and it reads an explicit frequency: null as absent and re-stamps "monthly" from the CEL default. So an insert never produced the blank; { frequency: null } on a still-recurring item is the one write that did, and nothing refused it. Pinned both ways in test/cadence-conditional-defaults.test.ts, the masking included — it is the assumption the rule's scope rests on.
  • The fan-out is silent, not loud. The issue expects a blank to be replicated onto every duty, "each of whom would then individually trip duly_duty's own recurring_needs_frequency on their first save". Measured, it does not: applyCatalogHandler copies frequency verbatim, that insert hits the same default-masking, and each duty is stamped "monthly" before any rule sees it. So one blank template becomes N duties dispatching on a cadence nobody chose and diverged from the catalog that defines them — not N refusals.

That second point is what makes the rule load-bearing rather than tidy: the catalog item is the last place the blank can be caught, not merely the first.

Tests

test/cadence-conditional-defaults.test.ts — the rule itself, in the suite that already owns both objects' cadence claims: the refused update (envelope-asserted, and the row proven untouched by it), the insert-masking measurement, controls proving the rule stays silent for standing (whose blank frequency is required by standing_no_frequency — an over-firing rule would make the pair jointly unsatisfiable) and for one_off, and a structural pin that both objects declare the rule under one name with one message.

test/catalog-apply-cadence.test.ts (new) — the apply path, because a rule that fires on a direct write but leaves apply unprotected is half a fix. Dispatched through the app's own executeAction against a real booted engine: catalog-instantiate.test.ts's FakeEngine runs no validations and stamps no defaults, which is exactly what is under test here. Covers the frequency reaching every duty apply creates, the silent-monthly replication above, and a standing item applying to standing duties with no cadence at all.

Reverse verification. With the rule deleted from the committed tree (clean deletion, restored by an EXIT/INT/TERM trap; mutation confirmed on disk by grep count and file size before the run, and the first attempt was declared void when its anchor missed): refuses an update that blanks frequency… and the structural pin go red, 30 of 32 still pass, and — the point — pnpm validate stays green on the ablated tree. There is no author-time gate for this; the tests are the whole guard. The apply-path suite stays green under ablation by design: it characterises the path the rule protects, not the rule.

Not fixed here — filed as #79

Covering the apply path against the real dispatcher surfaced a separate defect: applyCatalogHandler passes an ObjectQL query envelope ({ where: … }) to ctx.engine.find, and the runtime's buildActionEngineFacade wraps whatever it is given in a where of its own. Through the real dispatcher the read becomes { where: { where: … } }, comes back empty with no error, and duly_catalog_apply reports a successful run of zero. Measured: same item, same params, runtime facade → created: 0; flat facade → created: 1.

Different defect class from this card and a different file surface, so it is filed rather than ridden along. The last describe in the new suite pins it as a tripwire — it goes red when #79 is fixed, and is written to be deleted then, not adjusted.

Gates

All four green at 1f0aa43, the final commit on this branch:

✓ Validation passed (413ms)
Test Files 21 passed (21)
Tests 545 passed (545)
✓ Build complete (614ms)

pnpm validate's one warning — the hierarchy-security capability provider — is the expected state of this checkout per AGENTS.md rule 7, and is not silenced.

No changeset. This repo has no changeset mechanism: no .changeset/ now or anywhere in git log --all, no @changesets/* dependency, no script, no CI step, no mention in AGENTS.md. Creating one would mint a mechanism nothing reads. Confirmed with the coordinator mid-task.


Generated by Claude Code

os-warrenand others added 2 commits September 1, 2026 09:46
`duly_duty` has always carried `recurring_needs_frequency`;
`duly_catalog_item` never did. #61 mirrored the three standing /
non-recurring cadence rules onto the catalog item and left the converse
direction open.
The gap is only reachable on UPDATE: `applyFieldDefaults` runs on INSERT
only and reads an explicit `frequency: null` as absent, re-stamping the
CEL default, so `{ frequency: null }` on a still-recurring item was the
one write that produced the state, and nothing refused it.
Downstream does not catch it either. `applyCatalogHandler` copies
`frequency` onto every duty it creates and that insert hits the same
default-masking, so the duty is stamped "monthly" and `duly_duty`'s own
rule never fires — one blank template becomes N duties dispatching on a
cadence nobody chose, not N loud refusals. That makes the catalog item
the last place the blank can be caught.
Tests: the refusal and its negative controls go in the existing cadence
suite; `test/catalog-apply-cadence.test.ts` covers the apply path
against a real booted engine, including a tripwire on a separate,
pre-existing defect found while writing it — the runtime's action-engine
facade re-wraps the handler's `where`, so `duly_catalog_apply` finds
nothing and reports a successful run of zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Filed as #79 after the suite was written.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
@os-warren
os-warren marked this pull request as ready for review September 1, 2026 09:58
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

Reviewed — merging. The two corrections to my card are the valuable part.

Gates, re-run by me on 1f0aa43 in a clean review worktree:validate 0, typecheck 0, test 0 (Test Files 21 passed, Tests 545 passed), build 0.

The rule itself is three lines of metadata mirroring duly_duty, which is what the card asked for. What makes this PR worth reading is that you measured the card's narrative and found two parts of it wrong, then said so instead of quietly implementing around them:

  1. INSERT can never reach the gapapplyFieldDefaults reads an explicit frequency: null as absent and re-stamps the CEL default, so UPDATE is the only write that produces the state. My card implied both.
  2. The downstream half of my story does not happen. I assumed a blank would fan out and each generated duty would trip duly_duty's own rule. It does not: the duty insert hits the same default-masking, so a blank fans out as N duties silently stamped monthly. That is worse than what I described, and it makes the catalog item the last place the blank can be caught rather than a redundant second gate. The card is stronger for the correction.

The ablation discipline is the other thing I want on the record. Two attempts were declared void rather than re-run to a green: the first missed its anchor (the rule is preceded by a comment block) and the on-disk guard caught it before anything was measured; the second substituted a placeholder that broke stack load, so both suites reported no tests — a load failure wearing the costume of a result. Only the third produced a reading. That is exactly the failure mode this repo keeps hitting, and refusing to accept a measurement you do not trust is worth more than the rule you landed.

Noting for the record that pnpm validate stayed green on the ablated tree — there is no author-time gate for this gap, so the tests carry the whole claim. Worth remembering the next time someone proposes that a clean validate means a rule is enforced.

#79 — you were right to stop and ask, and right about the answer

Triaged as priority:p0 and dispatched immediately, with your option A + C's upstream half filed alongside, for your reason rather than for the size of the fix: the cost is not the dead action, it is that the suite covering it is green against a query shape production cannot produce. Every future change to those handlers is being verified against a fiction. That is the same defect class as everything else this round, and it is the one that compounds.

Option B was tempting and is wrong — "nothing calls it yet" is exactly when a wrong convention is cheapest to fix and most likely to be copied.


Generated by Claude Code

@os-warren
os-warren merged commit dbe1537 into mainSep 1, 2026
1 check passed
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.

duly_catalog_item has no recurring_needs_frequency-equivalent validation

1 participant

@os-warren