Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

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

Declare hierarchy-security and restore the manager depth grants - #56

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security
Sep 1, 2026
Merged

Declare hierarchy-security and restore the manager depth grants#56
os-warren merged 1 commit into
mainfrom
claude/issue-46-declare-hierarchy-security

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#46

Four places in this repo said declaring requires: ['hierarchy-security'] would fail an open-edition boot. It does not. That belief is what forced the three manager depth grants down to own, so removing it and restoring the grants are one change.

Verified on 5dd788b (final commit) with @objectstack/security-enterprisenot installed and nothing added to package.json.

What changed

objectstack.config.tsrequires: ['automation', 'hierarchy-security'], and the comment beside it rewritten. It now quotes the platform's own reason for the check, records the measurement, and says plainly that the warning is the expected state. The "Security posture" comment further down said the scopes "fail closed to owner-only, silently"; that was the same claim in its second half, and it is now the warning-announced behaviour it actually is.

src/security/permission-sets.tsduly_manager reads unit_and_below on duly_task and duly_duty. duly_admin gets the task depth by inheritance, not restatement, so the third grant costs no third edit. Both overrides spread the member entry, leaving writeScope: 'own' inherited rather than retyped. HIERARCHY_SCOPES_DEFERRED and its docblock are gone.

Write scopes did not move. A manager reads down and writes nothing but their own — that was never the deferred half.

AGENTS.md rule 7 — rewritten end to end: a hierarchy scope requires the declaration; omitting it is an author-time hard error that takes validate, build and every test importing the config; declaring it warns and installs nothing; open-edition resolution is owner-only and a local manager view showing only your own rows is the edition, not a bug. It also names the trapdoor next door — org is not a hierarchy scope, loads with no declaration, and discloses the whole tenant.

docs/product/data-model.md and docs/deployment/security.md — same claim, same correction. The deployment page's "Two manager grants are currently narrower than this table implies" section is replaced by one describing the declaration and the warning; the model table now shows the manager depth instead of "inherited".

Tests

test/security.test.ts. The old stopgap block pinned the compromise in both directions; the new one pins the dependency in both directions:

  • each of the three grants is unit_and_below;
  • those three are the only hierarchy scopes authored anywhere (which is also what keeps the next assertion from going vacuous);
  • if any hierarchy scope is authored, stack.requires must contain hierarchy-security — a named failure here instead of an opaque config-load failure across every suite;
  • no set reads duly_task at org;
  • the manager depth overrides keep every member key they do not change (the failure mode this change could most easily have caused: retyping the entry and dropping writeScope: 'own').

One deviation, stated

The card asks for a test that no grant anywhere has a write scope wider than own. Taken literally that is false today: duly_admin.duly_catalog_item is writeScope: 'org', deliberately — public_read is read-open but write-owned, so without it an administrator could only edit catalog items they personally created. So it is written as two assertions instead of one:

  • every write scope is ownbar one allowlisted exception, asserted by equality against that allowlist, so adding a second exception is a deliberate act rather than a silent pass;
  • no write scope anywhere is a hierarchy scope, unconditional and exception-included. This is the acceptance line's real content — depth never reaches the write axis — and it is the invariant this PR could have broken.

The second one earns its place: with the capability declared, the platform check accepts a hierarchy scope on either axis. It does not distinguish read from write. Nothing but this test stops a write depth from loading now.

Ablations

Four legs, each mutation confirmed on disk before measuring, each restored by an EXIT/INT/TERM trap. Tests resolve app code by relative path through vitest's transform — no package exportsdist boundary and no vitest alias (there is no vitest config file) — so no rebuild leg applies here.

legmutationpredictedobserved
1drop hierarchy-security from requiresvalidate redexit 1, defineStack hierarchy-scope capability validation failed (3 issues), naming all three grants — duly_admin.duly_task among them, which also proves the inherited grant is real
2duly_manager.duly_task.writeScope: 'org'write-scope tests red, validategreen6 failed / 54 passed; expected { …(3) } to deeply equal { Object (duly_admin.duly_catalog_item) }. Config loaded — org needs no declaration, which is the point
3duly_manager.duly_task.writeScope: 'unit_and_below'leg 2's failures plus the hierarchy-write assertion8 failed / 52 passed; duly_manager.duly_task writes by hierarchy depth: expected [ 'own_and_reports', 'unit', …(1) ] to not include 'unit_and_below'. Config still loaded
4duly_manager.duly_task.readScope back to 'own'depth assertions red, write assertions green6 failed / 54 passed; write-scope block stayed green, as predicted

One honesty note on the on-disk confirmation: the injected-marker count (MUTANT, want 1) was correct on every leg and is what proves each mutation landed. The paired removed-anchor count was mis-anchored — its pattern also matched the untouched duly_duty override — so it read 2/1/1 instead of 0. The marker count and the assertion output naming the mutated value are the evidence; that counter is not.

Gates

All four green on 5dd788b, run under the container's shared verify lock:

✓ Validation passed (397ms)
⚠ Capability "hierarchy-security" is provided by @objectstack/security-enterprise …
Test Files 12 passed (12)
Tests 374 passed (374)
✓ Build complete (635ms)

The warning is the acceptance criterion, not a leftover.

File-surface note

One file beyond the declared surface: src/security/index.ts, which re-exported HIERARCHY_SCOPES_DEFERRED. Deleting the constant without it is a compile error, so it is the mechanical completion of the card's own step 2 rather than new scope — a four-line export list losing one line. No sibling card in this round touches it (#51 datasets, #50duty.object.ts, #27 global actions). src/objects/duty.object.ts was not touched.

Found outside the file surface — reported, not edited

docs/roadmap.md (M2 bullet) says an open-edition checkout "correctly shows owner-only rows". That half is true and stays true. It does not repeat the falsified claim, so it is left alone.


Generated by Claude Code

`objectstack.config.ts`, AGENTS.md rule 7 and two docs all said that
declaring `requires: ['hierarchy-security']` would fail an open-edition
boot. Measured false: it warns, naming the provider package, and every
gate stays green. The claim is what forced the three manager depth
grants down to `own`.
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 06:05
@os-warren
os-warren merged commit 8033674 into mainSep 1, 2026
1 check passed
os-warren pushed a commit that referenced this pull request Sep 1, 2026
Picks up #54 (source defaults to self) and #56 (hierarchy-security). Both
touch objects this guard resolves against; no field was renamed or removed,
only `default:` flags on select options.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqkTcrxUFci7nqXdbBSe2p
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@os-warren