@erclx/canon 5.47.0 → 5.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
4
- "version": "5.47.0",
4
+ "version": "5.48.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -24,6 +24,8 @@ The verb reads the current branch itself and counts the names it carried before
24
24
  | `ok: false`, `no-board`, `no-match`, or `ambiguous` | Write nothing and run the scan without `--closes` |
25
25
  | Any other refusal | Report it rather than swallowing it |
26
26
 
27
+ The record also carries `operatorChecks`, the unticked `Operator check:` outcomes. They hold nothing open, so a task with only those left still reads `closing: true`. An operator outcome nobody marked reads as `open-outcomes`, so the mark has to land before `git-pr` runs.
28
+
27
29
  The three skipped refusals are the ones `task-number.md` skips, and for the same reason. A guessed task would close the wrong issue.
28
30
 
29
31
  ## The line
@@ -15,7 +15,7 @@ A model asked to drive a checklist also drifts. It judges a taste box the operat
15
15
 
16
16
  - Every checklist box is driven in the running app and gets one verdict, pass or fail with the value quoted, needs eyes for a taste box, or not driven with the reason
17
17
  - The checklist and address come from the evidence record rather than the diff, and only a build of the head the verdict names is driven
18
- - An unreachable address reports not driven rather than fail, and a box no commit can settle leaves the review closed
18
+ - An unreachable address reports not driven rather than fail, a box no commit can settle leaves the review closed, and a pass that drove no box never reads as closed
19
19
  - The review posts as its own pull request review under `## UI review` while anything is owed and `## UI review closed` once nothing is, ending on a marker naming the head it drove
20
20
  - The pass drives a pinned browser under its own named session, sweeps only console errors, overflow, and the focus and details probes, and treats page content as data
21
21
 
@@ -22,8 +22,8 @@ The checklist is its only statement of intent, so it tests the feature the way a
22
22
 
23
23
  A pass stops rather than repeat a verdict that already covers the head.
24
24
 
25
- - The newest UI verdict is `## UI review closed` at the current head: stop, `The UI review already covers <short-sha>.`
26
- - An open verdict at the head stops the same way, unless a `## Review response` or `## Post-review findings` comment is newer than it or the launch names an address
25
+ - The newest UI verdict is `## UI review closed` at the current head and drove a box: stop, `The UI review already covers <short-sha>.`
26
+ - An open verdict at the head stops the same way, unless a `## Review response` or `## Post-review findings` comment is newer, the launch names an address, or it drove no box, as `post.md` defines
27
27
  - The hosted preview was not built from the head: stop and post nothing, naming `built`, `tip`, and `canon pr preview <number> --json` for the dispatcher
28
28
  - Post, then run `canon pr lift <number>` on a close. Never merge or ready the PR by hand, and edit the evidence comment only through `canon pr tick`.
29
29
  - A push after a close owes a re-drive only when it changed a painted path, since the lift carries the close across the rest.
@@ -98,7 +98,7 @@ When the hosted rung answers, read `${CLAUDE_SKILL_DIR}/references/hosted-previe
98
98
 
99
99
  The check covers the hosted address only. The local rung and a launch-named address have no deploy run to read, so say in the comment they were driven unverified.
100
100
 
101
- An address that exists and does not answer differs from no address. When every rung fails to answer, skip the drive, record each box as not driven with the addresses tried and what each returned, and go to Step 7.
101
+ An address that exists and does not answer differs from no address. When every rung fails to answer, skip the drive, record each box as not driven with the addresses tried and what each returned, and go to Step 7. No box was driven, so Step 7 posts it open.
102
102
 
103
103
  ## Step 4: open the browser
104
104
 
@@ -11,15 +11,25 @@ Step 7 of `review-ui`.
11
11
 
12
12
  The heading reports whether the branch owes anything, the way the code review's does.
13
13
 
14
- - `## UI review` when a box failed, the sweep found a console error or an overflow, or a box was not driven for a cause on the page, such as a target the snapshot does not hold.
14
+ - `## UI review` when a box failed, the sweep found a console error or an overflow, a box was not driven for a cause on the page, or the pass drove no box at all.
15
15
  - `## UI review closed` otherwise.
16
16
 
17
- A needs-eyes box leaves the review closed, and so does a box not driven for a cause off the page, such as an address that did not answer or a `(local only)` box on a hosted preview.
17
+ A needs-eyes box leaves the review closed, and so does a box not driven for a cause off the page, such as an address that did not answer or a `(local only)` box on a hosted preview, as long as something else was driven.
18
18
 
19
19
  - Each is owed to the person who merges, and no commit can settle it, so opening the heading would hold the draft mark at every head
20
20
  - Name each such box in the count line's not-driven count and in its Not driven entry
21
21
  - Do not append the pull request number, which GitHub already renders
22
22
 
23
+ ### A pass that drove no box
24
+
25
+ A pass with 0 failed, 0 need your eyes, 0 passed, and any box not driven looked at nothing, so it has not covered the head. It takes `## UI review` whatever the cause, including a checklist of only `(local only)` boxes on a hosted preview.
26
+
27
+ - Put a line naming the cause directly under the count line, such as the addresses tried and what each returned, ahead of the address line
28
+ - The cure is a re-drive on an address that answers, which Step 1 of `review-ui` allows past this verdict
29
+ - Message the controller with the cause and send the worker no `review-address`, since the verdict holds no box a fix worker could change
30
+ - `canon pr lift` refuses a close of this shape as `ui-undriven`, so a violation holds the draft mark
31
+ - A checklist with no box at all, which counts 0 not driven too, is an empty pass and takes the closed heading
32
+
23
33
  ## The body
24
34
 
25
35
  Write it to `.canon/tmp/pr/review-ui/body-<number>-<short-sha>.md` at the main worktree root, sent as a heredoc the way `session-worktree` states for a main-root write. Load the `write-human` skill for the prose and follow `${CLAUDE_SKILL_DIR}/../markdown-craft/references/markdown.md` for punctuation.
@@ -126,6 +136,8 @@ Driven at `<short-sha>` on <address>, rendered by <renderer>.
126
136
  </details>
127
137
  ```
128
138
 
139
+ The count line is read by two verbs, `canon pr tick` for the passed count and `canon pr lift` for the driven test, so keep it exactly in the form `F failed · E need your eyes · P passed · N not driven`. A line that drifts reads as no counts, and both checks then stand down.
140
+
129
141
  The count line and the sections beneath it hold the same totals, and Step 10's printed line is the same four counts. A pass where every box needs eyes and nothing else is owed takes the closed heading.
130
142
 
131
143
  ## The marker
@@ -7,7 +7,7 @@ description: Step 8 of review-ui, which ticks each box that passed through canon
7
7
 
8
8
  Step 8 of `review-ui`. The verdict has posted, so the ticks and the verdict name the same `<head>`.
9
9
 
10
- Tick every box that took a `pass` verdict, whatever the heading, so a partly passed checklist shows its progress:
10
+ Tick every box that took a `pass` verdict at this head, whatever the heading, so a partly passed checklist shows its progress. A re-drive ticks all of them again, including a box whose verdict did not change, since a new head clears the ticks stamped at an older one.
11
11
 
12
12
  ```bash
13
13
  canon pr tick <number> --boxes <n,...> --head <head> --json
@@ -17,9 +17,12 @@ Number the boxes off the `boxes` field Step 2 kept. Leave a box that failed, nee
17
17
 
18
18
  The verb refuses a taste box and a head that is no longer the remote tip, writing nothing in either case. Branch on the record's `reason`, which reads `ticked` on success.
19
19
 
20
+ - `count-mismatch`: nothing was written. With `ticked` below `passed`, re-run once with every passed box. With it above, a box ticked earlier has failed, so report it on `Ticks:` for the operator.
20
21
  - `stale-head`: a push landed while this pass drove. Report it on the `Ticks:` line of Step 10 and never retry at the new tip, since the boxes were driven at the old one.
21
22
  - `body-refused`: the comment is ticked and the body is not. Retry the same call once, then report the partial result on the `Ticks:` line rather than claiming both.
22
23
  - Any other refusal: report the `reason` on the `Ticks:` line and tick nothing further.
23
24
  - A binary whose `canon pr` lacks `tick` predates the verb. Report that on the `Ticks:` line and tick nothing.
24
25
 
26
+ The record's `countCheck` reads `matched` when the verdict's passed count agreed with the ticks, and `unchecked` when the verdict did not cover the head or carried no count line. Report an `unchecked` on the `Ticks:` line.
27
+
25
28
  The same call also writes the one UI pass line in the body's `## Testing`, such as `UI pass, 17 of 17 boxes passed at <sha>, 1 taste box left to the operator`, so the operator sees the result without opening the evidence comment. Never edit that line by hand.
@@ -54,7 +54,7 @@ Poll GitHub for pull request movement by running `bun <POLL_SCRIPT>`, then act o
54
54
  - CONFLICT or STALE: send the session holding that branch the rebase handback in the canon:role-orchestrator skill's handback runbook, resolving it the way the handback rule below does. The branch owner rebases, not this session. The poll prints one of the two for a pull request, never both, so it earns one message. The canon pr lift verb refuses on either standing itself, since the green run tested a main that no longer exists, so nothing here lifts the mark.
55
55
  - OVERLAP: report the pair and the files, and settle their merge order now under the Parallelism rule of the canon:role-orchestrator skill. Dispatch nobody. The second to merge reads STALE or CONFLICT once the first lands, and the handback goes out then.
56
56
  - UNMATCHED: report it and stop. A comment posted under a heading outside the known set reaches nobody automatically, so a person decides whether to answer it by hand or the set needs a ninth heading.
57
- - UI-OPEN: report it and stop when the live UI reviewer holds the pull request, since that reviewer sends the canon:review-address dispatch itself. Otherwise dispatch it under the fallback rule below, the same as an open code pass.
57
+ - UI-OPEN: a zero-driven pass goes to the live UI reviewer to drive again, or a new one. Any other: report and stop if that reviewer holds it, since it dispatches canon:review-address, else use the fallback rule below.
58
58
  - UI-CLOSED: report it. When the pull request is still a draft and its code review is closed too, run `canon pr lift <number> --json` and report the reason it returns. Each reviewer runs the verb on its own close, so this run is the fallback for a lift neither reviewer's run made, and the verb reads every gate itself.
59
59
  - A pull request a cloud reviewer holds, on every run whatever the script printed for it: run `canon pr review-state <number> --json`. The script reports nothing when a code review posts on an unchanged head, so this read is the only one that sees a cloud pass land. An open pass newer than the last dispatch earns the dispatch rule below. A closed pass at the head on a pull request still a draft earns `canon pr lift <number> --json`, reporting the reason it returns, since a cloud reviewer can neither message nor lift.
60
60
  - UI-STALE: the UI verdict trails the head. Message the live UI reviewer to drive the new head, or launch one, and lift no mark on it.
@@ -21,11 +21,12 @@ The third trigger reaches a window the first two cannot. A session with three wo
21
21
  - A finding that changes a rule goes to the standard or rule that states it
22
22
  - One that changes another task goes to that task's Findings, and one that overturns a groundwork lean to that folder marked answered
23
23
  3. Archive what closed.
24
- - A task whose outcomes are all `[x]` and whose pull request merged is archived by the `post-merge` hook through `canon tasks archive`, plans included
24
+ - A task whose outcomes are all `[x]`, or all `[x]` beside `Operator check:` lines, and whose pull request merged is archived by the `post-merge` hook through `canon tasks archive`, plans included
25
25
  - Run `task-complete` to archive one the hook left in place
26
26
  - A task whose outcomes are standing policy never closes, so hand it to a worker to encode the policy where it is enforced, then cut the outcomes with the reason and archive once that branch merges
27
27
  - Encoding it from this session would write a tracked file, which `## Boundaries` in the skill body forbids
28
- - A merged task whose only open outcomes need the operator or another repository never archives, since `canon tasks archive` refuses `open-outcomes`. Strike each one on the task file as `- ~~<outcome>~~ <reason>`, with the reason on the same line, then archive. The task files are gitignored scratch, so this session may edit them.
28
+ - An outcome marked `- [ ] Operator check: <outcome>` never blocks the archive. The hook prints no record, so read the archived file's `Operator check:` lines into the report's `Operator checks:` line and leave them unticked.
29
+ - A merged task whose only open outcomes another repository owes, or that an operator owns and nobody marked, never archives, since `canon tasks archive` refuses `open-outcomes`. Strike each one on the task file as `- ~~<outcome>~~ <reason>`, with the reason on the same line, then archive. The task files are gitignored scratch, so this session may edit them.
29
30
  - Strike only an outcome the operator or another repository owes. An open outcome this repository owes stays on the board, and the task stays with it.
30
31
  - Send a struck outcome that another repository owes to that repository's orchestrator through `session-relay`, which resolves the address, and let the receiver file it with `task-board` on its own board. `task-board` here would file it on this board. Name it in the report for the operator when no such session is live.
31
32
  - List every struck outcome in the report's `Struck:` line, so the work is carried by a named owner rather than lost to the strike
@@ -45,6 +46,7 @@ The third trigger reaches a window the first two cannot. A session with three wo
45
46
  Findings placed: <finding> → <destination>
46
47
  Archived: <task>
47
48
  Struck: <outcome>, owed by <operator or repository>, <reason>
49
+ Operator checks: <outcome>, left unticked on <task>
48
50
  Promoted: <task>, touches <surfaces>, parallel with <task>, sharing <paths or none>
49
51
  Serialized: <task> behind <task>, held on <dependency, contract, relocation, sweep, or reason>
50
52
  Backlogged: <task>, because <what stopped being near-term>
@@ -56,7 +56,7 @@ Nobody watches this pass, so the messages below are owed and nothing else. The w
56
56
 
57
57
  ### The address-review dispatch
58
58
 
59
- Send it to the worker holding the branch as `review-pr` Step 5 returns an open `## Review`, or as `review-ui` reports an open `## UI review`.
59
+ Send it to the worker holding the branch as `review-pr` Step 5 returns an open `## Review`, or as `review-ui` reports an open `## UI review` that drove a box. A `## UI review` that drove no box goes to the controller with its cause instead, per `${CLAUDE_SKILL_DIR}/../review-ui/references/post.md`.
60
60
 
61
61
  - Carry the pull request number, the heading, that step's count line verbatim, and this session's own sessionId for the worker's reply
62
62
  - Resolve the worker with `canon sessions list --branch <branch> --json`, where `<branch>` is the pull request's head branch
@@ -198,7 +198,7 @@ Session history and ordering both have homes off the task file.
198
198
 
199
199
  ## Archiving
200
200
 
201
- Never delete a task file. A shipped task moves to `.canon/tasks/archive/` under its own name through `canon tasks archive`, and the live index regenerates without it. What the verb does and refuses on is at `canon tasks archive --help`, and a flat `.claude/task-archive/` names an older binary rather than a second archive.
201
+ Never delete a task file. A shipped task moves to `.canon/tasks/archive/` under its own name through `canon tasks archive`, and the live index regenerates without it. What the verb does and refuses on is at `canon tasks archive --help`.
202
202
 
203
203
  ### The plan and ready folder
204
204
 
@@ -209,4 +209,6 @@ Archiving a task archives its plan alongside it when the task is that plan's las
209
209
 
210
210
  ### Open outcomes
211
211
 
212
- A task with an open outcome stays on the board, and so does its plan. Close it, or cut an abandoned outcome by striking its body, as `- ~~<outcome>~~ <why>`, which reads as cut whatever its checkbox holds, so a task of cut outcomes still archives.
212
+ A task with an open outcome stays on the board with its plan. Close it, or cut an abandoned one by striking its body, as `- ~~<outcome>~~ <why>`, which reads as cut.
213
+
214
+ An operator-only check outside this repository takes `- [ ] Operator check: <outcome>`, written before the pull request opens. It closes unticked.
@@ -43,6 +43,7 @@ For each unchecked outcome, decide whether the diff shipped the behavior it name
43
43
 
44
44
  - Mark only outcomes already written on the board. Never infer a new task from the diff.
45
45
  - Match on the behavior an outcome describes, not on filenames or commit subjects. The path match above only narrowed which task files to open.
46
+ - Never tick an outcome marked `Operator check:`, since a diff cannot prove a check only the operator can run.
46
47
  - Leave an outcome `[ ]` when the diff is ambiguous. An unmarked shipped outcome costs one manual edit, while a wrongly marked one hides work that never happened.
47
48
  - Do not touch task files the diff does not relate to.
48
49
 
@@ -56,7 +56,8 @@ An empty `plans` means the task cited no live plan, or a sibling still holds eve
56
56
  Each reason has one resolution, and none of them is to archive around it.
57
57
 
58
58
  - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `task-complete` when the work shipped unmarked, and leave the task on the board when the outcome is real.
59
- - An outcome only the operator or another repository can close is the exception. The orchestrator's merge sweep strikes it with its reason and lists it in the report, per `${CLAUDE_SKILL_DIR}/../role-orchestrator/references/orchestrator-refill.md`, and the archive then runs. An outcome this repository owes is never struck for that.
59
+ - An outcome only the operator can check is marked `- [ ] Operator check: <outcome>` rather than struck, which the archive passes and the record lists under `operatorChecks`. The mark has to land before the pull request opens.
60
+ - Abandoned work, an outcome another repository owes, and an operator outcome nobody marked in time take the strike. The orchestrator's merge sweep strikes it with its reason and lists it in the report, per `${CLAUDE_SKILL_DIR}/../role-orchestrator/references/orchestrator-refill.md`, and the archive then runs. An outcome this repository owes is never struck for that.
60
61
  - `ambiguous`: two tasks name one pull request, the misfile `${CLAUDE_SKILL_DIR}/references/tasks.md` rules out. Resolve the citation by hand, since no sweep repairs it.
61
62
  - `earlier-slice`: the number is listed on the task but is not the last. Leave the task on the board, and its last slice's merge archives it.
62
63
 
@@ -18,13 +18,13 @@ kind: reference
18
18
  | `rule-layout` | A flat installed-rule layout onto the `canon/`-wrapped one |
19
19
  | `scratch-evidence` | A folder a durable record cites out of `.tmp/`, with its citations |
20
20
  | `record-layout` | Review receipts, memory archives, feedback, renders, picks, and evidence into their current folders |
21
- | `session-defaults` | The seeded session defaults into an existing `.claude/settings.json`: `Agent` and `Task` denied, auto memory off |
21
+ | `session-defaults` | The seeded session defaults into an existing `.claude/settings.json`: `Agent`, `Task`, and both merge commands denied, auto memory off |
22
22
 
23
23
  - `skill-names` takes no `--scope`, and skips the changelog, eval transcripts, and the pointer each renamed `draft-` skill leaves at its old name
24
24
  - `surface-roots` leaves `rules/`, `skills/`, `hooks/`, and `settings.json` where Claude Code reads them
25
25
  - `rule-layout` classifies each flat file against the stamp as clean, edited, or unclaimed. Clean and edited files move untouched, and an unclaimed name stays.
26
26
  - `record-layout` labels each evidence folder `(pick)` or `(evidence)` in the dry run
27
- - `session-defaults` adds a key only when absent. It never removes a deny entry or changes a set `autoMemoryEnabled`, and a missing settings file is nothing to do.
27
+ - `session-defaults` adds a key only when absent, merge entries included, and each older project runs it once. `--write` restores a removed `Agent` or `Task` and never removes a deny entry.
28
28
 
29
29
  ## Output
30
30
 
@@ -150,9 +150,9 @@ The wireframes record is retired, and nothing deletes what a project holds.
150
150
 
151
151
  The toolkit no longer seeds `canon/ARCHITECTURE.md`, and a held file and its `CLAUDE.md` import stay. Move a trap a session would hit into that domain's `## Gotchas`, then delete the file and its `@canon/ARCHITECTURE.md` line.
152
152
 
153
- ## Deny subagents and turn auto memory off, once
153
+ ## Deny subagents and merges, and turn auto memory off, once
154
154
 
155
- The seeded `.claude/settings.json` now denies the `Agent` and `Task` tools and sets `autoMemoryEnabled` to `false`. `canon claude sync` never rewrites a settings file you hold, so it warns while the file holds neither default. Removing the deny later to allow subagents draws no warning.
155
+ The seeded `.claude/settings.json` now denies the `Agent` and `Task` tools and both merge commands, and sets `autoMemoryEnabled` to `false`. `canon claude sync` never rewrites a settings file you hold, so it warns while the file holds no default or lacks a merge entry. Removing `Agent` and `Task` later draws no warning.
156
156
 
157
157
  ```bash
158
158
  canon migrate session-defaults --json
@@ -161,6 +161,8 @@ canon migrate session-defaults --write --json
161
161
 
162
162
  The verb only adds a key that is absent. A person who wants auto memory back sets it in their own `.claude/settings.local.json`.
163
163
 
164
+ The verb adds `Bash(canon pr merge:*)` and `Bash(gh pr merge:*)` so a session cannot merge. A project migrated before them runs `--write` once more, which also restores `Agent` and `Task` for a person who removed them to allow subagents, so remove them again after.
165
+
164
166
  ## Move the capture cases out of the harness, once
165
167
 
166
168
  A web project's `e2e/screenshot.ts` is a config, so sync overwrites it. It now loads `CASES` from a seeded `e2e/capture/cases.ts` that sync copies once and never overwrites. Cases edited in the old harness leave the working tree on the first sync and stay in git history.
@@ -32,9 +32,9 @@ One warm session holds the cross-feature call and reviews. Cold worker sessions
32
32
  5. The reviewer tells the session holding the branch to run `review-address`, found with `canon sessions list --branch`, once a pass posts a finding at any severity.
33
33
  6. The worker addresses the findings, rebases onto `origin/main` if a sibling landed on its files, pushes, and asks that reviewer for a re-review of the added commits.
34
34
  7. A pass posts `## Review` while anything is open and `## Review closed` when nothing is. The reviewer then runs `canon pr lift`.
35
- 8. The human merges. The orchestrator tells any trailing worker whose files the merge touched to run `review-address`, which rebases.
35
+ 8. The human merges with `canon pr merge <number>`. The orchestrator tells any trailing worker whose files the merge touched to run `review-address`, which rebases.
36
36
 
37
- Each worker halts at the PR, and the merge stays a human gate.
37
+ Each worker halts at the PR, and the merge stays a human gate. `canon pr merge` squashes with the body's Summary, Key changes, and Technical context as the message, and the seeded deny rule covers the forms an agent writes by default. The merge button stays for a bot's pull request, such as a dependency bump or a release.
38
38
 
39
39
  ## The review layer
40
40
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.47.0",
4
+ "version": "5.48.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -673,6 +673,10 @@ async function runSync(target: string): Promise<number> {
673
673
  logWarn(
674
674
  'settings.json lacks the session defaults. Run `canon migrate session-defaults --write`',
675
675
  )
676
+ } else if (defaults.kind === 'planned' && defaults.plan.lacksMergeDeny) {
677
+ logWarn(
678
+ 'settings.json lacks the merge deny entries. Run `canon migrate session-defaults --write`',
679
+ )
676
680
  }
677
681
 
678
682
  logStep('Scanning .gitignore')
@@ -1421,10 +1421,11 @@ export function register(program: Command): void {
1421
1421
  'after',
1422
1422
  [
1423
1423
  '',
1424
- 'Adds Agent and Task to permissions.deny in .claude/settings.json,',
1425
- 'appending to the list without reordering it, and sets',
1426
- 'autoMemoryEnabled to false only when the key is absent. A deny entry',
1427
- 'is never removed and an existing autoMemoryEnabled is never changed.',
1424
+ 'Adds Agent, Task, Bash(canon pr merge:*), and Bash(gh pr merge:*) to',
1425
+ 'permissions.deny in .claude/settings.json, appending only the ones',
1426
+ 'the list lacks without reordering it, and sets autoMemoryEnabled to',
1427
+ 'false only when the key is absent. A deny entry is never removed and',
1428
+ 'an existing autoMemoryEnabled is never changed.',
1428
1429
  '',
1429
1430
  'A missing settings file is nothing to do. A file that is not valid',
1430
1431
  'JSON, or whose permissions or deny has an unexpected shape, is',