@bevel-software/platform-core-backend 0.34.0 → 0.35.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.
- package/agent-guide/access-control.md +146 -300
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +16 -11
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +65 -0
- package/agent-guide/tool-manuals.md +132 -154
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/create-core-server.js +1 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +6 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +7 -4
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +22 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +3 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +75 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +23 -4
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +103 -13
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +45 -31
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +67 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +4 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +10 -8
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +6 -35
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +85 -39
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/skills/skills.tools.js +1 -1
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +27 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +12 -8
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +115 -44
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +31 -10
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +22 -18
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +3 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +71 -21
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/package.json +4 -3
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +25 -0
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/create-core-server.ts +1 -1
- package/src/core/create-core-services.ts +13 -3
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -12
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/access-control.interface.ts +26 -0
- package/src/modules/access/access-control.service.ts +92 -0
- package/src/modules/access/access.routes.ts +23 -4
- package/src/modules/access/index.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +79 -25
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +49 -31
- package/src/modules/agent-guide/agent-guide.ts +120 -13
- package/src/modules/agent-guide/guide-budget.ts +73 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +4 -4
- package/src/modules/agent-instructions/compose.ts +4 -0
- package/src/modules/agent-instructions/shared-file-rules.ts +10 -8
- package/src/modules/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/mcp/mcp.service.ts +5 -35
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
- package/src/modules/secrets-vault/index.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +91 -39
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/skills/skills.tools.ts +1 -1
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +96 -5
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/tool-manuals.contract.ts +22 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +138 -46
- package/src/modules/tool-manuals/tool-manuals.tools.ts +53 -14
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
- package/src/modules/workflow/agent-tools/workflow.tools.ts +22 -18
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +8 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +156 -49
- package/src/modules/workspace/agent-access.gate.ts +1 -1
- package/src/modules/workspace/workspace.tools.ts +74 -21
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/read-capped.ts +40 -0
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
|
@@ -1,157 +1,115 @@
|
|
|
1
1
|
## Access control
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`roles.yaml` (who
|
|
5
|
-
`access.md` files (who may do what, where).
|
|
3
|
+
Who may read and change each path, on every branch, is set by three kinds of
|
|
4
|
+
file: `roles.yaml` (who holds which role), `groups.yaml` (who is in which
|
|
5
|
+
group) and `access.md` files (who may do what, where). A folder everyone may
|
|
6
|
+
read and the Platform Team may change has this `access.md`:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
path for a `roles.yaml` that has lost its last Admin. The App roles page
|
|
18
|
-
shows that account under Admin as a fixed member that cannot be added or
|
|
19
|
-
removed there; every other Admin membership is exactly what the file says,
|
|
20
|
-
and removing one takes effect on that person's next request.
|
|
21
|
-
- **Plugins are grantable principals.** `plugin/<name>/read`,
|
|
22
|
-
`plugin/<name>/write` and `plugin/<name>/owner` in any access file mean
|
|
23
|
-
everyone who currently holds that verb on the plugin whose manifest `name`
|
|
24
|
-
is `<name>`, derived live from the plugin's own `access.md`. Any spelling
|
|
25
|
-
folds to the identifier (`plugin/GTM/read` and `plugin/gtm/read` are one
|
|
26
|
-
principal). This is how a shared skill is made visible to a plugin's
|
|
27
|
-
members: `read: plugin/gtm/read` on the skill's folder.
|
|
28
|
-
Adding or removing someone on the plugin changes what they can read
|
|
29
|
-
everywhere the token is granted, with no copying.
|
|
30
|
-
- **Access rules** live in `access.md` files, which carry **two blocks with two
|
|
31
|
-
scopes**: the BODY (below the closing `---`) declares the rules for the
|
|
32
|
-
folder the file sits in, and the FRONTMATTER declares who may read and
|
|
33
|
-
write that `access.md` itself. Each block names verbs (`read`, `write`,
|
|
34
|
-
`download`, `owner`) whose entries are either grants (a bare principal) or
|
|
35
|
-
denials (the lowercase word `deny`, a space, then the principal).
|
|
36
|
-
Capitalised forms like `Deny` are *not* triggers; they are treated as part
|
|
37
|
-
of a name.
|
|
38
|
-
- **Principals** are a role (`role/<Name>`, from `roles.yaml`), a group
|
|
39
|
-
(`group/<Name>`, from the active group source — `groups.yaml`, or
|
|
40
|
-
`synced-groups.yaml` when groups sync from an identity provider), a person
|
|
41
|
-
as `Name <email>`, a plugin
|
|
42
|
-
token (above), or
|
|
43
|
-
**`everyone`** — the built-in org-wide principal: every signed-in person and
|
|
44
|
-
their agents. `read: everyone` in a folder's BODY opens that folder to the
|
|
45
|
-
whole organisation; it is how an organisation-wide skill or plugin is
|
|
46
|
-
shared. The same line in a file's FRONTMATTER only makes that one file
|
|
47
|
-
visible — a plugin's `access.md` ships with `read: everyone` in its
|
|
48
|
-
frontmatter so the plugin can be found and joined, and that admits nobody
|
|
49
|
-
to the plugin itself. A person's personal plugin
|
|
50
|
-
(`{{pluginsDir}}/personal-<id>/`) grants its owner access and denies
|
|
51
|
-
`everyone` outright, so opening a parent folder never opens it.
|
|
52
|
-
**Write roles as `role/<Name>` and groups as `group/<Name>`** — for
|
|
53
|
-
example `read: group/Platform Team` or `deny role/Reviewer`; the Manage
|
|
54
|
-
access dialog writes them that way too. A bare name (`read: Reviewer`)
|
|
55
|
-
still works and means the ROLE when a role of that name exists, and the
|
|
56
|
-
group otherwise. So a group that shares a role's name is reached only as
|
|
57
|
-
`group/<Name>`: a bare `read: Admin` reaches the `Admin` role, never the
|
|
58
|
-
members of a group named `Admin` (unless `roles.yaml` itself lists that
|
|
59
|
-
group under the role). The same holds for a denial: a bare
|
|
60
|
-
`deny Admin` denies the role, so denying a same-named group needs
|
|
61
|
-
`deny group/Admin`. `role/<Name>` always means the role,
|
|
62
|
-
`group/<Name>` always the group.
|
|
63
|
-
- **Keep an `access.md` body pure YAML**, with any explanation in `#` comments.
|
|
64
|
-
A body that does not parse as YAML naming at least one verb is read in the
|
|
65
|
-
older format instead, where the FRONTMATTER carried the folder's rules — so a
|
|
66
|
-
stray line of prose silently changes which block governs the folder.
|
|
67
|
-
- **The verbs nest.** `owner` sits over `write` and `download`; `write` and
|
|
68
|
-
`download` each sit over `read` — anyone who may edit a node, or save a copy
|
|
69
|
-
of it, may also view it. `write` and `download` say nothing about each other.
|
|
70
|
-
The nesting is GRANT-ONLY: a grant of a higher verb confers the lower ones,
|
|
71
|
-
but `deny write` or `deny download` says nothing about `read` and never
|
|
72
|
-
strips a separate read grant. So `download: Ana <ana@x.io>` alone lets Ana
|
|
73
|
-
open the node as well as download it, and a `deny download` beside an
|
|
74
|
-
inherited read leaves her able to open it but not save it.
|
|
75
|
-
- **You can only change what you can read.** Nothing is created, changed,
|
|
76
|
-
moved into or removed from a place the caller cannot read — on every
|
|
77
|
-
branch, drafts included, whatever `write:` rules say. A write tool refused
|
|
78
|
-
for this says so (`write-denied`, naming the unreadable folder), and
|
|
79
|
-
proposing is not offered either: a proposal into a folder its author cannot
|
|
80
|
-
see would vanish from them the moment it landed. Two exceptions. A NEW
|
|
81
|
-
FOLDER directly under `{{knowledgeBaseDir}}/`, `{{skillsDir}}/` or
|
|
82
|
-
`{{pluginsDir}}/`: anyone may start one, whatever the root's rules grant
|
|
83
|
-
them, and the new folder's `access.md` is seeded with the creator's own
|
|
84
|
-
`read:` grant so what they put there is visible to them (a loose FILE
|
|
85
|
-
directly at a root has no folder to carry that grant and is not excepted).
|
|
86
|
-
And an Admin — or the deployment owner — may change the files directly in
|
|
87
|
-
the repository root (`roles.yaml`, `access.md`, `groups.yaml`, …) even when
|
|
88
|
-
the root grants read to nobody: the same rescue the write
|
|
89
|
-
floor gives them, so a tree whose root rules lock everyone out stays
|
|
90
|
-
repairable from inside the app. That rescue stops at the root; a subfolder
|
|
91
|
-
an admin cannot read is closed to them like to anyone else.
|
|
92
|
-
- **Resolution** walks repo root → file directory, accumulating per-principal
|
|
93
|
-
state. User-level entries trump role-level entries. A role denial removes
|
|
94
|
-
only that role's contribution; it does not undo grants from other roles.
|
|
95
|
-
- **`roles.yaml` is editable only by Admin** — hard-coded in the resolver,
|
|
96
|
-
never overridable by an `access.md`.
|
|
97
|
-
- **`access.md` files are picked up at any depth**, so a folder can tighten or
|
|
98
|
-
widen what it inherited from its parent.
|
|
99
|
-
- **Per-file rules exist for Markdown notes and `.tool` definitions.** A note
|
|
100
|
-
(`.md`, lowercase) may name verbs in its own frontmatter, and those rules
|
|
101
|
-
apply to that one note; a `.tool` definition keeps the access verbs in its
|
|
102
|
-
own YAML the same way; a distribution may register further file kinds
|
|
103
|
-
that carry their own rules. Any other file (a PDF, a presentation, a
|
|
104
|
-
spreadsheet, an image, any binary, a `.markdown` or `.MD` file, or binary
|
|
105
|
-
content saved as `.md`) takes its folder's rules: sharing it on its own is refused with
|
|
106
|
-
`folder-governs-access`, naming the folder. To change who
|
|
107
|
-
can open such a file, change its folder's `access.md`, or move the file to a
|
|
108
|
-
folder whose rules fit.
|
|
109
|
-
|
|
110
|
-
Rules are enforced at runtime; a malformed `roles.yaml` or `access.md` surfaces
|
|
111
|
-
when access is resolved.
|
|
8
|
+
```yaml
|
|
9
|
+
---
|
|
10
|
+
owner:
|
|
11
|
+
- role/Admin
|
|
12
|
+
---
|
|
13
|
+
read:
|
|
14
|
+
- everyone
|
|
15
|
+
write:
|
|
16
|
+
- group/Platform Team
|
|
17
|
+
```
|
|
112
18
|
|
|
113
|
-
|
|
19
|
+
- **An `access.md` has two blocks.** The body, below the closing `---`, holds
|
|
20
|
+
the rules for the folder the file sits in. The frontmatter above it says who
|
|
21
|
+
may read and change that `access.md` itself. Keep the body pure YAML, with
|
|
22
|
+
any explanation in `#` comments: a body that does not parse as YAML naming at
|
|
23
|
+
least one verb is read in an older format, where the frontmatter carried the
|
|
24
|
+
folder's rules.
|
|
25
|
+
- **Verbs and entries.** The verbs are `read`, `write`, `download` and
|
|
26
|
+
`owner`. Each verb takes a list, one entry per line, as above: a one-line
|
|
27
|
+
value such as `write: everyone` does not parse, and the whole file is
|
|
28
|
+
skipped. Each entry is a grant (a principal) or a denial (the lowercase word
|
|
29
|
+
`deny`, a space, then the principal). `Deny` with a capital is part of a
|
|
30
|
+
name, not a denial.
|
|
31
|
+
- **Verbs nest.** `owner` includes `write` and `download`, and each of those
|
|
32
|
+
includes `read`: a grant gives the verbs below it, so a `download` grant
|
|
33
|
+
alone lets a person open a page as well as save a copy.
|
|
34
|
+
A denial takes away the verbs above it: `deny read` also removes `write`,
|
|
35
|
+
`download` and `owner`, and `deny write` removes `owner`. `deny write` and
|
|
36
|
+
`deny download` never remove `read`.
|
|
37
|
+
- **Principals** are a role (`role/<Name>`, from `roles.yaml`), a group
|
|
38
|
+
(`group/<Name>`, from the active group source), a person as
|
|
39
|
+
`Name <email>`, a plugin token, or **`everyone`**: every signed-in
|
|
40
|
+
person and their agents. `everyone` under `read` in a folder's body opens the
|
|
41
|
+
folder to the organisation; in a file's frontmatter it only makes that file
|
|
42
|
+
visible (a plugin's `access.md` has it there so the plugin can be found and
|
|
43
|
+
joined, which admits nobody to the plugin). Write roles and groups with
|
|
44
|
+
their prefix, as the app does: a bare name means the role when one has that
|
|
45
|
+
name, the group otherwise, in a denial too. A person's
|
|
46
|
+
personal plugin (`{{pluginsDir}}/personal-<id>/`) grants its owner access and
|
|
47
|
+
denies `everyone` outright, so opening a parent folder never opens it.
|
|
48
|
+
- **Plugin tokens.** `plugin/<name>/read`, `plugin/<name>/write` and
|
|
49
|
+
`plugin/<name>/owner` mean everyone the body of that plugin's own
|
|
50
|
+
`access.md` grants that verb or a higher one (grants inherited from folders
|
|
51
|
+
above the plugin, and `deny` lines, do not count). `<name>` is the
|
|
52
|
+
manifest's `name`, in any spelling. This is how a shared skill reaches a
|
|
53
|
+
plugin's members: `plugin/gtm/read` under `read` on the skill's folder.
|
|
54
|
+
- **The nearest rule decides, verb by verb.** For a path and a verb, the
|
|
55
|
+
platform looks at the file's own rules (below), then its folder's
|
|
56
|
+
`access.md`, then each folder above, up to the root, and the first one that
|
|
57
|
+
says anything about you for that verb decides; a nearer `read` rule leaves a
|
|
58
|
+
`write` grant from a folder above in force. Within
|
|
59
|
+
one file, a rule naming you beats one for your roles or groups; a grant
|
|
60
|
+
through one of your roles beats a denial through another; `everyone` comes
|
|
61
|
+
last. No rule anywhere means no access: a folder no rule opens to you is
|
|
62
|
+
invisible to you.
|
|
63
|
+
- **Per-file rules** exist for markdown notes (`.md`, lowercase) and `.tool`
|
|
64
|
+
files: verbs in a note's frontmatter, or in a `.tool`'s YAML, apply to that
|
|
65
|
+
file alone. Any other file (a PDF, an image, a spreadsheet, a `.markdown` or
|
|
66
|
+
`.MD` file) takes its folder's rules, and sharing it on its own is refused
|
|
67
|
+
with `folder-governs-access`, naming the folder. Change the folder's rules,
|
|
68
|
+
or move the file to a folder whose rules fit.
|
|
69
|
+
- **You can only change what you can read**, on every branch, drafts
|
|
70
|
+
included, whatever `write` says. Such a write is refused with `write-denied`,
|
|
71
|
+
naming the place you cannot read, and proposing it is not offered: it would
|
|
72
|
+
vanish from you the moment it landed. Two exceptions. Anyone may start a new
|
|
73
|
+
folder directly under `{{knowledgeBaseDir}}/`, `{{skillsDir}}/` or
|
|
74
|
+
`{{pluginsDir}}/`, and its `access.md` is seeded with the creator's own read
|
|
75
|
+
grant (a loose file directly at a root is not excepted); on a protected
|
|
76
|
+
branch you still need write there, so start it on a draft. And an Admin may
|
|
77
|
+
always change the files at the repository root (`roles.yaml`, `access.md`,
|
|
78
|
+
`groups.yaml`), so a tree that locks everyone out stays repairable.
|
|
79
|
+
- **Broken rules.** A `roles.yaml` that does not parse stops access from
|
|
80
|
+
resolving until it is fixed. An `access.md` that does not parse is skipped
|
|
81
|
+
(its folder then follows the folders above it) with no error you can see, so
|
|
82
|
+
after editing one, check the result with `file_stat` and `explainAccess`.
|
|
83
|
+
|
|
84
|
+
### Roles are pre-set: a "new role" is usually a group
|
|
114
85
|
|
|
115
86
|
**What a role is.** A role in `roles.yaml` is an app role: a capability the
|
|
116
|
-
platform defines and acts on (`Admin` is one),
|
|
117
|
-
|
|
118
|
-
|
|
87
|
+
platform defines and acts on (`Admin` is one), with the people who hold it. The
|
|
88
|
+
set of roles is pre-set. A role is not a way to name a team. The deployment
|
|
89
|
+
admin (the server's `ADMIN_EMAIL`) is always an Admin, whatever the file says.
|
|
119
90
|
|
|
120
|
-
**Agents never create roles.** Add people to a role that
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
422 that names the role and says: app roles are pre-set — add people to
|
|
91
|
+
**Agents never create roles.** Add people to a role that exists, or remove
|
|
92
|
+
them, and nothing more: never add or rename a role in `roles.yaml`. Such a
|
|
93
|
+
write is refused with a 422 saying that app roles are pre-set: add people to
|
|
124
94
|
existing roles, and use a GROUP for a task- or team-scoped set of people.
|
|
125
|
-
Relay that
|
|
126
|
-
write the file.
|
|
127
|
-
|
|
128
|
-
**Is it really a group?** When someone asks for a "new role", it almost always
|
|
129
|
-
is. It is a group when any of these hold:
|
|
95
|
+
Relay that to your user as it stands.
|
|
130
96
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
97
|
+
**Is it really a group?** It is when the name says who the people are (a team,
|
|
98
|
+
a project, a customer) rather than a capability the platform has, when it
|
|
99
|
+
would change when the project ends or the team reshuffles, or when its purpose
|
|
100
|
+
is to give those people access to some folders.
|
|
135
101
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
1. If an existing role already carries the capability, add the people to it.
|
|
142
|
-
2. Otherwise make it a group: add or extend the group in `groups.yaml` (or
|
|
143
|
-
point your user at the app's Groups page), then grant the group in the
|
|
144
|
-
`access.md` of the folders it should reach.
|
|
145
|
-
3. If your user still needs a role the platform does not have, that is not an
|
|
146
|
-
edit you can make — say so, and leave the decision to an admin.
|
|
102
|
+
**What to do instead.** Add the people to an existing role that carries the
|
|
103
|
+
capability. Otherwise make it a group: add it to `groups.yaml` (or point your
|
|
104
|
+
user at the app's Groups page), then grant the group in the `access.md` of the
|
|
105
|
+
folders it should reach. A role the platform does not have is an admin's
|
|
106
|
+
decision, not an edit you can make.
|
|
147
107
|
|
|
148
108
|
### Giving a role to a group
|
|
149
109
|
|
|
150
|
-
A role's
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
identity provider, `groups.yaml` otherwise. Here a `Reviewer` role the
|
|
154
|
-
deployment already has goes to a whole group:
|
|
110
|
+
A role's members may include a group: write `- group:<Name>`, naming a group
|
|
111
|
+
in the active group source (`synced-groups.yaml` when groups are synced from
|
|
112
|
+
the organisation's directory, `groups.yaml` otherwise):
|
|
155
113
|
|
|
156
114
|
```yaml
|
|
157
115
|
roles:
|
|
@@ -162,166 +120,54 @@ roles:
|
|
|
162
120
|
- group:Platform Team
|
|
163
121
|
```
|
|
164
122
|
|
|
165
|
-
- **Matching
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
is restored to what the default branch has, so a role edit drafted on a
|
|
197
|
-
branch is dropped at the merge without a word. Do not propose one. If your
|
|
198
|
-
user is an Admin, `edit_file` the file on the default branch directly — for
|
|
199
|
-
example, to give the Reviewer role to a group, add the entry under the
|
|
200
|
-
existing role:
|
|
201
|
-
|
|
202
|
-
```yaml
|
|
203
|
-
roles:
|
|
204
|
-
Admin:
|
|
205
|
-
- dana@example.com
|
|
206
|
-
Reviewer:
|
|
207
|
-
- lee@example.com
|
|
208
|
-
- group:Platform Team # added
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
If your user is not an Admin, tell them who is (the `Admin` entries in
|
|
212
|
-
`roles.yaml`) and that the change is made in the app's Roles page or by an
|
|
213
|
-
admin editing the file; do not open a change request for it.
|
|
123
|
+
- **Matching** is case- and whitespace-insensitively against the active group
|
|
124
|
+
source, as for role names.
|
|
125
|
+
- **An unknown group is a validation error**: the write is refused with a 422
|
|
126
|
+
naming the entry and its role, and nothing is saved.
|
|
127
|
+
- **A group under `Admin` makes every member a full admin**, including anyone
|
|
128
|
+
added to the group later. Make that edit only when your user is an Admin and
|
|
129
|
+
asks for exactly that, and say so in the summary. `Admin` must keep at least
|
|
130
|
+
one member listed by email.
|
|
131
|
+
- **Members add up**: the role's members are the emails listed plus everyone
|
|
132
|
+
currently in each group.
|
|
133
|
+
- **A denial** of the role (`deny role/Reviewer`) removes the role's
|
|
134
|
+
contribution for everyone in the group, as for the emails. To deny the
|
|
135
|
+
group instead, write `deny group/Platform Team`.
|
|
136
|
+
|
|
137
|
+
**Only an Admin changes `roles.yaml`, on a protected branch, directly.** On a
|
|
138
|
+
draft anyone who can read the file may save it, but a merge puts back the
|
|
139
|
+
target branch's copy, so a role edit drafted on a branch is dropped without a
|
|
140
|
+
word. Do not propose one. If your user is an Admin, `edit_file` the file on
|
|
141
|
+
`{{defaultBranch}}`. If not, tell them who the Admins are (the `Admin` entries)
|
|
142
|
+
and that the change is made in the app's Roles page or by an Admin.
|
|
143
|
+
|
|
144
|
+
### Before you say or change who has access
|
|
145
|
+
|
|
146
|
+
Look it up, folder by folder: one folder's rules say nothing about another's.
|
|
147
|
+
`file_stat` on each path that matters, with `explainAccess: true`, lists who
|
|
148
|
+
holds each verb (`roster`, when you may manage the path's access); the active
|
|
149
|
+
group source (`synced-groups.yaml` when groups are synced, `groups.yaml`
|
|
150
|
+
otherwise) says who is in each group, the person you are helping included. It
|
|
151
|
+
does not judge one named person for you. Then name the people: who can do it
|
|
152
|
+
now and, for a move or a rule change, who gains and who loses. When they
|
|
153
|
+
already have it, say so and change nothing.
|
|
214
154
|
|
|
215
155
|
### Direct writes vs change requests
|
|
216
156
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
affected file with an eligible approver needs that approval, whatever its
|
|
225
|
-
type (notes, binary files, files without an extension).
|
|
226
|
-
- Agents carry exactly their user's access, never more. Before writing to the
|
|
227
|
-
default branch, **ask the user** whether to write directly or go through the
|
|
228
|
-
review flow — and prefer a change request when in doubt, when the change is
|
|
229
|
-
large, or when it touches content the user does not own.
|
|
157
|
+
- A person, or an agent acting for them, with **write or owner on every file a
|
|
158
|
+
change touches** may write directly to a protected branch. Prefer a change
|
|
159
|
+
request when in doubt, when the change is large, or when it touches content
|
|
160
|
+
they do not own.
|
|
161
|
+
- Without that access, the change goes through a **draft and a change
|
|
162
|
+
request**, approved by an owner or writer of each affected file that has one.
|
|
163
|
+
Agents carry exactly their user's access, never more.
|
|
230
164
|
- **Check `canWrite` before offering a direct write.** `list_skills` reports
|
|
231
|
-
it for a skill's `SKILL.md`; `get_skill` for every file of the skill (or,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
can manage that path's access — who holds each verb: who can approve.
|
|
241
|
-
The one exception is `roles.yaml`: a change request cannot carry that edit
|
|
242
|
-
(see above), so `canWrite: false` there means the change is an Admin's to
|
|
243
|
-
make, in the app's Roles page or directly on the default branch — do not
|
|
244
|
-
open a request for it.
|
|
245
|
-
|
|
246
|
-
### An agent proposes and syncs; a person merges
|
|
247
|
-
|
|
248
|
-
- **Propose** with `open_change_request`, then give the user the request's
|
|
249
|
-
`url`. Reviewing, approving and merging a change request happen in the app,
|
|
250
|
-
by a person — no agent tool approves a file, bypasses approval, or merges a
|
|
251
|
-
request. `merge_change_request` no longer exists.
|
|
252
|
-
- **Edit** an open request's title or description with `update_change_request`
|
|
253
|
-
— its author, or anyone who may merge it (an Admin, or someone who may
|
|
254
|
-
approve every file it changes); anyone else is refused, and so is a merged or
|
|
255
|
-
closed request. `description` replaces the request's description (the `body`
|
|
256
|
-
`get_change_request` answers) whole; readers see the new text at once.
|
|
257
|
-
- **Sync** a draft with `update_branch` on the draft, `from` = the branch the
|
|
258
|
-
request targets. This is allowed while the draft's request is open, and is
|
|
259
|
-
how you bring it up to date. Left out, `from` is the draft's own shared
|
|
260
|
-
state: for when it moved while your workspace held commits of its own
|
|
261
|
-
(`branch_status` says `behindRemote`). `merge_branch` (`source` = the
|
|
262
|
-
target, `target` = the draft) does the same without conflict handling.
|
|
263
|
-
- **Resolve conflicts** where `update_branch` answers `conflicts`: for every
|
|
264
|
-
conflicted path it carries the file at the merge base, on your side and on
|
|
265
|
-
theirs, and the working copy with git's markers — and it has left the draft
|
|
266
|
-
exactly as it was. A side is null when that side has no file, or when
|
|
267
|
-
`truncated` names it (too large, or not text): then read `ours` with
|
|
268
|
-
`read_file` (it is the draft's own file), `theirs` with `read_file_at` at
|
|
269
|
-
`from` (the branch, not a commit — its tip is not on the draft's history
|
|
270
|
-
until the merge lands), and `base` with `read_file_at` at the answer's
|
|
271
|
-
`mergeBase`. A side that is not text cannot be read that way (those
|
|
272
|
-
reads are text-only): settle such a file whole, with `choose` — `content`
|
|
273
|
-
is for text. A conflicted file you may not read on one of the two
|
|
274
|
-
branches is left out and only counted (`withheldConflicts`): it is not
|
|
275
|
-
yours to settle, so ask a person who can read it to update the draft in
|
|
276
|
-
the app. Decide each
|
|
277
|
-
file shown (keep both sides' information unless one is clearly stale;
|
|
278
|
-
`list_file_changes` says who changed what and why), then call
|
|
279
|
-
`update_branch` again **with the same `from`** and `resolutions`, one per
|
|
280
|
-
conflicted path: `content`, the resolved text, or `choose` (`ours`,
|
|
281
|
-
`theirs`, `delete`). A resolution is a write on your draft: you must be
|
|
282
|
-
able to read the file at both commits the answer names (`ours`, the
|
|
283
|
-
draft's; `theirs`, the merged-in branch's), and it takes the rule every
|
|
284
|
-
save on a draft takes — you may read the file, or it is new — never a
|
|
285
|
-
write permission. It merges, commits as you and publishes. A publish the
|
|
286
|
-
host refuses, or a replay that conflicts because the shared branch moved
|
|
287
|
-
meanwhile, answers with the saved-locally error: the merge is committed
|
|
288
|
-
on your draft but NOT shared — `branch_status` shows it ahead. Do not
|
|
289
|
-
report the draft as shared then; the next save or `share_current_branch`
|
|
290
|
-
carries the commit, and a replay that conflicts is handed to recovery.
|
|
291
|
-
Nothing else is written that way: an ordinary edit goes through the file
|
|
292
|
-
tools. You have no git command; there is never a half-done merge on disk
|
|
293
|
-
for you to finish.
|
|
294
|
-
- `merge_branch` refuses to merge a draft into the branch its open change
|
|
295
|
-
request targets — it names the request; ask the user to review it in the
|
|
296
|
-
app. Into a protected branch it merges only what you could commit there
|
|
297
|
-
directly, under the rule above — and never a change to `roles.yaml`, whoever
|
|
298
|
-
you are: roles are changed in the app, not merged in from a draft.
|
|
299
|
-
- **Know where a draft stands** with `branch_status`: ahead/behind, whether
|
|
300
|
-
the shared branch moved, the files written but not committed yet, the
|
|
301
|
-
protected branch it was forked from, and `changedPaths` — what it changes
|
|
302
|
-
against that base, the files a change request would carry. The list holds
|
|
303
|
-
only the files you may read; `withheldPaths` counts the rest, so when it
|
|
304
|
-
is above zero the list is not the request's whole set. Ask it before
|
|
305
|
-
proposing a draft.
|
|
306
|
-
- **Read history** with `list_file_changes` (a file's commits: who, when,
|
|
307
|
-
subject), `read_file_at` (the file at a commit, or on another branch) and
|
|
308
|
-
`compare_file` (the diff of a commit, or between two branches). Each takes
|
|
309
|
-
the read the file itself takes. To put an earlier version back, read it
|
|
310
|
-
with `read_file_at` and write it with `write_file`.
|
|
311
|
-
- **Delete** a draft with `delete_branch`, which removes it for everyone. Only
|
|
312
|
-
its author (`<email-localpart>/…`, or your own `suggestions/…` bundle) or an
|
|
313
|
-
Admin may. Preview with `dryRun: true` first: it changes nothing and says
|
|
314
|
-
whether the delete would go through, how many commits are not on the default
|
|
315
|
-
branch, and the last commit. A branch holding such commits is refused unless
|
|
316
|
-
you pass `discardUnmerged: true` — only when the user wants that work gone. An
|
|
317
|
-
open change request from or into the branch that proposes nothing is closed
|
|
318
|
-
by the delete. One that still proposes something refuses it, with a link:
|
|
319
|
-
its author can withdraw it, or an Admin can decline it, in the app — hand the
|
|
320
|
-
user that link. A request you cannot see is not named, and a preview leaves
|
|
321
|
-
it out, so a delete can still be refused after a clean preview. Hand the user the
|
|
322
|
-
`lastCommit` it answers, so the branch can be restored. The server removes a
|
|
323
|
-
draft on its own once its change request was merged, but only while nothing
|
|
324
|
-
has happened to it since: no commit that is not on the default branch, no
|
|
325
|
-
request open from or into it, no save still landing, no file held for
|
|
326
|
-
editing, and its last commit
|
|
327
|
-
the one that was merged. Any other draft stays until someone deletes it.
|
|
165
|
+
it for a skill's `SKILL.md`; `get_skill` for every file of the skill (or, with
|
|
166
|
+
`file`, for that file alone); `list_files` and `read_file` per path. It
|
|
167
|
+
answers for `{{defaultBranch}}` under the rules committed there, whichever
|
|
168
|
+
branch you read. `false` means use a draft and a change request. A read-only
|
|
169
|
+
connection gets `false` everywhere and can do neither. To explain a verdict,
|
|
170
|
+
call `file_stat` on `{{defaultBranch}}` with `explainAccess: true`: it says
|
|
171
|
+
why, and, when you may manage that path's access, who holds each verb (who
|
|
172
|
+
can approve). For `roles.yaml`, `false` means an Admin's change, not a
|
|
173
|
+
request.
|
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
## Conventions
|
|
2
2
|
|
|
3
|
-
These are conventions, not validations
|
|
4
|
-
them. They exist because a knowledge base people can
|
|
5
|
-
merely correct.
|
|
3
|
+
These are conventions, not validations: nothing rejects a file for breaking
|
|
4
|
+
them. They exist because a knowledge base people can find their way around
|
|
5
|
+
beats one that is merely correct.
|
|
6
6
|
|
|
7
7
|
1. **Descriptive file names.** `Weekly-Sync-2026-03-14.md` beats `notes3.md`.
|
|
8
|
-
Avoid spaces
|
|
8
|
+
Avoid spaces: a link to a name with a space needs `%20` in its place.
|
|
9
9
|
|
|
10
|
-
2. **Markdown links between documents.**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
the
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
can still point at the old path. Paths written in prose or code are not
|
|
21
|
-
links, and a move leaves them as they are.
|
|
10
|
+
2. **Markdown links between documents.** Write `[Page Name](relative/path/Page.md)`,
|
|
11
|
+
relative to the folder of the page that holds the link, so it resolves in
|
|
12
|
+
the app and on the git host. When you read a link, resolve it the same way.
|
|
13
|
+
`move_file` keeps most links working: it rewrites the links in the moved
|
|
14
|
+
files and in the other markdown pages it searches, and names what it left:
|
|
15
|
+
HTML pages, raw HTML inside a page, and pages you may not change. It leaves
|
|
16
|
+
`transcripts/` and `probes/` folders alone, even files moved out of them,
|
|
17
|
+
and pages you cannot read (the answer only says such pages may exist). A
|
|
18
|
+
move that would edit links in more than 200 files is refused unless you pass
|
|
19
|
+
`rewriteLinks: false`. Paths written in prose or code are not links.
|
|
22
20
|
|
|
23
|
-
3. **Absolute dates.** `YYYY-MM-DD`, never "last Tuesday"
|
|
21
|
+
3. **Absolute dates.** `YYYY-MM-DD`, never "last Tuesday": a saved file
|
|
24
22
|
outlives the moment it was written.
|
|
25
23
|
|
|
26
|
-
4. **Search before creating.**
|
|
27
|
-
|
|
24
|
+
4. **Search before creating.** `grep` for the subject and `list_files` the
|
|
25
|
+
folder first. If a page on the subject exists, extend it rather than start
|
|
26
|
+
a rival.
|
|
28
27
|
|
|
29
|
-
5. **Preserve what is there.** Append or edit sections; do not overwrite a
|
|
30
|
-
wholesale unless asked to.
|
|
28
|
+
5. **Preserve what is there.** Append or edit sections; do not overwrite a
|
|
29
|
+
file wholesale unless asked to.
|
|
31
30
|
|
|
32
|
-
6. **Say where it came from.** When a claim rests on a
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
it still true?".
|
|
31
|
+
6. **Say where it came from.** When a claim rests on a source (a person, a
|
|
32
|
+
ticket, a document, a URL), name it next to the claim, with the date it was
|
|
33
|
+
true. The next reader's first question is "says who, and is it still true?".
|