morgana: row 204 (193a, #1702) viewer level on R1's candidate 44e36fc4c: LEVELS collaborator and viewer, R1's level tests follow, packet updated
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# Row 193a build: the viewer level on R1's access file
|
||||
|
||||
Morgana (Opus 5.5), 2026-10-20. Gitea #1691, parent row 193, design `agents/morgana/work/queue-193/DESIGN.md`
|
||||
r5 (e2d161e2e on morgana/row193, approved at r4 by Duckworth, duckworth-0014). Go: sage-1373 item 3. Base:
|
||||
`refactor` at aa1bd209e. Lands after FED R1 and is rebased onto R1's landing.
|
||||
Morgana (Opus 5.5), 2026-10-20. Queue row 204, Gitea #1702 (parent row 193, #1691), design
|
||||
`agents/morgana/work/queue-193/DESIGN.md` r5 (e2d161e2e on morgana/row193, approved by Duckworth,
|
||||
duckworth-0018). Go: sage-1373 item 3. Chains on row 201 (FED R1) round 1 candidate gizmoduck/fed-r1 at
|
||||
44e36fc4c (sage-1412, Q177). Lands after row 201 and rebases onto its landing.
|
||||
|
||||
## What it is
|
||||
|
||||
@@ -11,21 +12,21 @@ Design 4.2: R1 refuses any grant level but `collaborator`. Row 193a widens that
|
||||
with the one asked for, so a viewer grant matches `{}` and `{level: 'viewer'}`, and fails
|
||||
`{level: 'collaborator'}`.
|
||||
|
||||
## Why the candidate holds only a test now
|
||||
## The change
|
||||
|
||||
R1 (`packages/business/src/access.mjs`) has no candidate yet. Sage ruled no stub on refactor (sage-1373
|
||||
item 3). So this branch carries:
|
||||
On R1's candidate (44e36fc4c):
|
||||
|
||||
- `packages/business/tests/access-levels.test.mjs`: the row 193a tests, written against R1's interface as
|
||||
FED r4.5 (acd5f1f42) sections 3.5 and 7 give it.
|
||||
- The source change, below, to apply on R1's candidate at rebase.
|
||||
- `packages/business/src/access.mjs`: `LEVELS` becomes `["collaborator", "viewer"]`. That is the one
|
||||
source line. R1's level check at :136 already names `grants[<i>].level`, so nothing else moves. Its
|
||||
comment now says what a viewer is.
|
||||
- `packages/business/tests/access.test.mjs` (R1's): three places pinned collaborator only. The `LEVELS`
|
||||
test now expects both levels. The refused-level test drops `viewer` from its bad list, adds `Viewer`,
|
||||
and both its regexes read `must be one of collaborator, viewer`. R1's `covers` test at :272
|
||||
(a collaborator grant fails `{level: 'viewer'}`) is unchanged and still holds.
|
||||
- `packages/business/tests/access-levels.test.mjs` (new): the row 193a tests.
|
||||
|
||||
Until R1 lands, the test file fails with `ERR_MODULE_NOT_FOUND` for `../src/access.mjs`. That is expected.
|
||||
|
||||
## The source change for the rebase
|
||||
|
||||
In R1's level check, the allowed set becomes `["collaborator", "viewer"]`. It is one line wherever R1 keeps
|
||||
the level rule. Its refusal message must name the key path `grants[<i>].level`, as R1's own rule does.
|
||||
No other test or script reads either source or test path (grep over `packages/*/tests` and `scripts/`).
|
||||
R1's own mutant M32 (`if (false) refuse(`) still applies to the same line.
|
||||
|
||||
## R1's interface (confirmed, gizmoduck-0029)
|
||||
|
||||
@@ -45,11 +46,13 @@ A grant with no `level` refuses (`undefined` in the refused list), which R1 also
|
||||
|
||||
## Evidence
|
||||
|
||||
Run on 2026-10-20 in the row193a worktree with `node --test --test-reporter=tap
|
||||
tests/access-levels.test.mjs` from `packages/business`.
|
||||
Run on 2026-10-20 in the row193a worktree with `node --test --test-reporter=tap <file>` from
|
||||
`packages/business`, each file alone:
|
||||
|
||||
- Base (no access.mjs): fails to load, `ERR_MODULE_NOT_FOUND`.
|
||||
- Throwaway reference: an `access.mjs` with only the level rule, a declared-business rule and FED's
|
||||
- On R1's candidate with the change: `access-levels.test.mjs` tests 4, pass 4, fail 0;
|
||||
`access.test.mjs` tests 24, pass 24, fail 0.
|
||||
- Before R1 (on aa1bd209e): the new file fails to load, `ERR_MODULE_NOT_FOUND`.
|
||||
- Throwaway reference, before R1: an `access.mjs` with only the level rule, a declared-business rule and FED's
|
||||
`covers`, placed in `src/`, run, then deleted, never committed. Result: pass 4, fail 0.
|
||||
|
||||
Mutants on that reference, each failing exactly one test:
|
||||
@@ -61,4 +64,4 @@ Mutants on that reference, each failing exactly one test:
|
||||
| `covers` ignores the asked level | 3, "covers tells a viewer grant from a collaborator grant" |
|
||||
| `covers` passes any grant when collaborator is asked | 3 |
|
||||
|
||||
At rebase, the same four mutants run again on R1's real code before the row goes to review.
|
||||
The same four mutants on R1's real code need Sage's slot (mutant batch). Results go here before review.
|
||||
|
||||
@@ -10,8 +10,8 @@ import { refuse } from "./errors.mjs";
|
||||
import { configDir } from "./business.mjs";
|
||||
import { isPlainObject, requireObject, rejectUnknownKeys, requireId, requirePositiveInt, readJsonFile, deepFreeze } from "./util.mjs";
|
||||
|
||||
// The grant levels. Row 193a widens this to collaborator and viewer.
|
||||
export const LEVELS = Object.freeze(["collaborator"]);
|
||||
// The grant levels (row 193a, #1691): a viewer reads threads shared with it and drives no instance.
|
||||
export const LEVELS = Object.freeze(["collaborator", "viewer"]);
|
||||
// Ids a user may never take: the bus's own principals.
|
||||
const RESERVED_USERS = Object.freeze(["human", "tester"]);
|
||||
const LOCAL_HOME = "local";
|
||||
|
||||
@@ -70,8 +70,8 @@ test("checkAccess does not freeze or alias the caller's document", () => {
|
||||
assert.notEqual(checked.grants, d.grants);
|
||||
});
|
||||
|
||||
test("LEVELS is the single allowed level set, collaborator only", () => {
|
||||
assert.deepEqual([...LEVELS], ["collaborator"]);
|
||||
test("LEVELS is the single allowed level set, collaborator and viewer (row 193a)", () => {
|
||||
assert.deepEqual([...LEVELS], ["collaborator", "viewer"]);
|
||||
assert.ok(Object.isFrozen(LEVELS));
|
||||
});
|
||||
|
||||
@@ -168,11 +168,11 @@ test("grants: instances is [\"*\"] or distinct declared instances of that busine
|
||||
assert.ok(checkAccess(mutated((d) => { d.grants[0].instances = ["researcher", "writer"]; }), context()));
|
||||
});
|
||||
|
||||
test("grants: any level other than collaborator refuses, naming grants[i].level", () => {
|
||||
for (const bad of ["viewer", "owner", "Collaborator", "COLLABORATOR", "", null, 1]) {
|
||||
refuses(() => checkAccess(mutated((d) => { d.grants[1].level = bad; }), context()), /grants\[1\]\.level must be one of collaborator/);
|
||||
test("grants: any level other than collaborator or viewer refuses, naming grants[i].level", () => {
|
||||
for (const bad of ["owner", "Collaborator", "COLLABORATOR", "Viewer", "", null, 1]) {
|
||||
refuses(() => checkAccess(mutated((d) => { d.grants[1].level = bad; }), context()), /grants\[1\]\.level must be one of collaborator, viewer/);
|
||||
}
|
||||
refuses(() => checkAccess(mutated((d) => { delete d.grants[0].level; }), context()), /grants\[0\]\.level must be one of collaborator \(got undefined\)/);
|
||||
refuses(() => checkAccess(mutated((d) => { delete d.grants[0].level; }), context()), /grants\[0\]\.level must be one of collaborator, viewer \(got undefined\)/);
|
||||
});
|
||||
|
||||
test("grants: expires is optional ISO time, and a past expiry is not refused", () => {
|
||||
|
||||
Reference in New Issue
Block a user