Watch
2
Three Forgejo guardfiles are a privilege ladder maintained as three hand-written files, and their resource vocabulary has already diverged #1365
Open
opened 2026-08-28 22:42:50 +00:00 by coilyco-ops
·
5 comments
No Branch/Tag specified
main
release
aos/claude/ee98-wardfreeze
chore/full-name-attribution
aos/claude/mt75-bundle-tag
aos/claude/ad84-revert-voice
aos/claude/ad84-voice
aos/claude/tc69-abspath
aos/claude/mt75-quiet-plan
voice-no-rarity-statements
aos/claude/mt75-netlify-remove
aos/claude/ad84-titles
aos/claude/ad84
aos/claude/mt75-aterm-fullscreen
aos/claude/mt75-netlify-wrap
aos/claude/mt75-kubectl-context
aos/claude/mt75
aos/claude/mt75-ward-cut
aos/claude/eb77
aos/claude/fp87-ward-schema
aos/claude/fp87-ward-posture
aos/claude/fp87
aos/claude/vt77
aos/1105-require-issue-labels
aos/claude/kb87-native-arch
aos/claude/kb87-window-identity
aos/claude/kb87
aos/claude/tg69
aos/claude/ff54-retire-issue-refs
aos/claude/ff54
aos/claude/rc44-bundle-version
aos/claude/mu55-contract-tests
aos/claude/mu55-sound-mark
aos/claude/rc44
aos/claude/mu55-identity-card
aos/claude/mu55-doctor
aos/claude/mu55-shadow-reap
aos/claude/mu55-drop-windows
aos/claude/mu55-dryrun-exits
aos/claude/mu55-list-json
aos/claude/mu55-title-order
aos/claude/mu55-overlay-contract
aos/claude/qu74
aos/claude/ue86
aos/claude/yq86
aos/claude/vk48-harness-set
aos/claude/vk48-default-agent
aos/claude/vk48-completion
aos/claude/vk48-release-fix
aos/claude/vk48
aos/claude/yb89
aos/claude/sb46
fix/agent-compose-pin-v3-roster
aos/claude/ve67-defer
aos/claude/ve67
aos/claude/ur54
aos/claude/tj49
feat/acompose-v3-roster
ops/393-retire-doc-size-alias
ops/393-drop-em-dash-check
feat/vendored-tree-exclude
aos/claude/xlarge-band
aos/claude/ue65
aos/claude/identity-color-wins
aos/claude/ap47
aos/claude/zr44
aos/claude/xk58
aos/claude/aw85-skill-size-owner
aos/claude/ym96-docs-bands
aos/claude/wt57-pin-aos-bundle
aos/claude/wt57-image-inputs-filter
aos/claude/ym96-label-taxonomy
ops/dev-base-pin-rust-1.90.0
aos/claude/mg96-clean
aos/claude/mg96
backup/fix/bake-precommit-hooks
rescue/aos-test-timeout
aos/claude/issues-977-979-agents-base
aos/claude/sx87
refactor/remove-context-budget-json
issue-946
aos/codex/20260806t050901z-50407-6291ab0a
aos/codex/standalone-shadow-workspace
backup/aos/codex/20260806t061240z-10127-754d7de2
aos/codex/standalone-local-service-route
aos/codex/aosterm-aoscompose-wrapper
aos/codex/agents-launch-profile-source
aos/codex/launch-profiles-yaml
aos/codex/20260806t031603z-7731-c76c17f2
backup/aos/codex/20260805t183628z-5916-617bb239
backup/aos/codex/20260805t025242z-30811-fbb135ff
aos/codex/aos-v2-roster-852
aos/codex/20260801t164712z-64119-69ee8bb6
backup/aos/codex/20260801t164900z-67616-2ad2d0e3
issue-834
aos/codex/pr-829-1130
issue-824-agent-proxy-model-routing
task-merge-pr818
fix/aos-ci-20260730
issue-671
issue-734
issue-484
issue-498
issue-622
issue-512
issue-679
issue-454
backup/issue-785-first-person
issue-785-first-person
director-pr784
restore-language-images
recovery/2026-07-28-triaged-branch-archive
recovery/2026-07-27-local-work
recovery/aos-local-build-20260727
codex/land-pr-733
codex/aos-ci-watch
issue-642
issue-682-goose-yaml
issue-656-goose-context
safety/aos-local-main-09347d0
issue-611-specialist-images
fix-action-run-list-page
issue-454-v2
experiment/no-ops-forgejo
feat/dev-base-image
aos-v0.275.0
v0.283.0
aos-v0.274.0
aos-precommit-v0.63.0
aos-v0.273.0
aos-v0.272.0
aos-precommit-v0.62.0
aos-v0.271.0
aos-v0.270.0
aos-precommit-v0.61.0
aos-precommit-v0.60.0
aos-v0.269.0
aos-v0.268.0
aos-precommit-v0.59.0
aos-precommit-v0.58.0
aos-v0.267.0
aos-precommit-v0.57.0
aos-precommit-v0.56.0
aos-eval-v0.12.0
aos-v0.266.0
aos-eval-v0.11.0
aos-eval-v0.10.0
aos-precommit-v0.55.0
aos-v0.265.0
aos-v0.264.0
aos-v0.263.0
aos-v0.262.0
aos-eval-v0.9.0
aos-v0.261.0
aos-v0.260.0
aos-v0.259.0
aos-v0.258.0
aos-v0.257.0
aos-precommit-v0.54.0
aos-precommit-v0.53.0
aos-precommit-v0.52.0
aos-precommit-v0.51.0
aos-precommit-v0.50.0
v0.282.0
v0.281.0
aos-v0.256.0
aos-v0.255.0
aos-v0.254.0
aos-v0.253.0
aos-v0.252.0
aos-v0.251.0
aos-v0.250.0
aos-v0.249.0
aos-v0.248.0
aos-v0.247.0
aos-v0.246.0
aos-v0.245.0
aos-v0.244.0
aos-v0.243.0
aos-v0.242.0
aos-v0.241.0
v0.280.0
aos-v0.240.0
aos-v0.239.0
aos-v0.238.0
aos-v0.237.0
aos-v0.236.0
aos-v0.235.0
aos-v0.234.0
aos-v0.233.0
aos-v0.232.0
aos-v0.231.0
aos-v0.230.0
aos-v0.229.0
aos-v0.228.0
aos-v0.227.0
v0.279.0
aos-v0.226.0
v0.278.0
aos-v0.224.0
aos-v0.223.0
v0.277.0
aos-precommit-v0.49.0
aos-v0.222.0
aos-precommit-v0.48.0
aos-eval-v0.8.0
aos-eval-v0.7.0
v0.276.0
aos-precommit-v0.47.0
aos-precommit-v0.46.0
aos-v0.221.0
aos-precommit-v0.45.0
aos-v0.220.0
aos-eval-v0.6.0
aos-precommit-v0.44.0
aos-v0.219.0
aos-v0.218.0
v0.275.0
aos-precommit-v0.43.0
aos-v0.217.0
aos-precommit-v0.42.0
aos-precommit-v0.41.0
aos-eval-v0.5.0
aos-precommit-v0.40.0
aos-precommit-v0.39.0
aos-v0.216.0
aos-precommit-v0.38.0
aos-precommit-v0.37.0
aos-precommit-v0.36.0
aos-v0.215.0
aos-precommit-v0.35.0
aos-v0.214.0
aos-precommit-v0.34.0
aos-precommit-v0.33.0
aos-precommit-v0.32.0
aos-precommit-v0.31.0
v0.274.0
aos-eval-v0.4.0
aos-eval-v0.3.0
aos-precommit-v0.30.0
aos-precommit-v0.29.0
aos-precommit-v0.28.0
aos-precommit-v0.27.0
aos-eval-v0.2.0
aos-precommit-v0.26.0
aos-eval-v0.1.0
aos-precommit-v0.25.0
aos-precommit-v0.24.0
aos-v0.213.0
aos-v0.212.0
aos-v0.211.0
aos-v0.210.0
aos-v0.209.0
aos-v0.208.0
aos-v0.207.0
aos-v0.206.0
aos-v0.205.0
aos-v0.204.0
aos-v0.203.0
aos-precommit-v0.23.0
v0.273.0
v0.272.0
aos-v0.202.0
aos-precommit-v0.22.0
v0.271.0
aos-v0.201.0
aos-v0.200.0
aos-precommit-v0.21.0
aos-v0.199.0
aos-v0.198.0
aos-precommit-v0.20.0
v0.270.0
aos-precommit-v0.19.0
aos-v0.197.0
aos-v0.196.0
v0.269.0
aos-v0.195.0
aos-v0.194.0
aos-v0.193.0
aos-precommit-v0.18.0
v0.268.0
v0.267.0
aos-precommit-v0.17.0
v0.266.0
aos-v0.192.0
aos-v0.191.0
aos-precommit-v0.16.0
aos-v0.190.0
aos-v0.189.0
aos-v0.188.0
aos-v0.187.0
aos-v0.186.0
aos-precommit-v0.15.0
aos-v0.185.0
aos-v0.184.0
aos-precommit-v0.14.0
aos-v0.183.0
v0.265.0
aos-v0.182.0
aos-v0.181.0
aos-v0.180.0
aos-v0.179.0
aos-precommit-v0.13.0
aos-v0.178.0
aos-precommit-v0.12.0
aos-v0.177.0
aos-precommit-v0.11.0
aos-v0.176.0
aos-v0.175.0
aos-v0.174.0
aos-precommit-v0.10.0
aos-v0.173.0
aos-v0.172.0
aos-v0.171.0
aos-v0.170.0
aos-v0.169.0
aos-v0.168.0
aos-v0.167.0
aos-precommit-v0.9.0
v0.264.0
aos-v0.166.0
aos-v0.165.0
aos-v0.164.0
aos-v0.163.0
aos-v0.162.0
aos-v0.161.0
v0.263.0
aos-v0.160.0
aos-v0.159.0
aos-precommit-v0.8.0
aos-v0.158.0
aos-v0.157.0
aos-precommit-v0.7.0
aos-v0.156.0
aos-v0.155.0
aos-v0.154.0
aos-v0.153.0
v0.262.0
aos-precommit-v0.6.0
aos-precommit-v0.5.0
aos-precommit-v0.4.0
aos-v0.152.0
aos-precommit-v0.3.0
aos-v0.151.0
aos-v0.150.0
aos-v0.149.0
aos-precommit-v0.2.0
aos-v0.148.0
aos-v0.147.0
aos-v0.146.0
aos-v0.145.0
aos-v0.144.0
aos-v0.143.0
aos-precommit-v0.1.0
aos-v0.142.0
aos-v0.141.0
aos-v0.140.0
aos-v0.139.0
aos-v0.138.0
aos-v0.137.0
aos-v0.136.0
aos-v0.135.0
aos-v0.134.0
aos-v0.133.0
aos-v0.132.0
aos-v0.131.0
aos-v0.130.0
aos-v0.129.0
aos-v0.128.0
aos-v0.127.0
aos-v0.126.0
aos-v0.125.0
v0.261.0
aos-v0.124.0
v0.260.0
aos-v0.123.0
aos-v0.122.0
aos-v0.121.0
aos-v0.120.0
aos-v0.119.0
aos-v0.118.0
aos-v0.117.0
aos-v0.116.0
aos-v0.115.0
aos-v0.114.0
aos-v0.113.0
aos-v0.112.0
aos-v0.111.0
aos-v0.110.0
aos-v0.109.0
aos-v0.108.0
aos-v0.107.0
aos-v0.106.0
aos-v0.105.0
aos-v0.104.0
v0.259.0
aos-v0.103.0
v0.258.0
aos-v0.102.0
aos-v0.101.0
aos-v0.100.0
aos-v0.99.0
aos-v0.98.0
aos-v0.97.0
aos-v0.96.0
aos-v0.95.0
aos-v0.94.0
aos-v0.93.0
aos-v0.92.0
aos-v0.91.0
aos-v0.90.0
aos-v0.89.0
v0.257.0
aos-v0.88.0
aos-v0.87.0
aos-v0.86.0
v0.256.0
aos-v0.85.0
aos-v0.84.0
aos-v0.83.0
aos-v0.82.0
aos-v0.81.0
aos-v0.80.0
aos-v0.79.0
aos-v0.78.0
aos-v0.77.0
aos-v0.76.0
aos-v0.75.0
aos-v0.74.0
aos-v0.73.0
aos-v0.72.0
aos-v0.71.0
aos-v0.70.0
aos-v0.69.0
aos-v0.68.0
aos-v0.67.0
aos-v0.66.0
aos-v0.65.0
aos-v0.64.0
aos-v0.63.0
aos-v0.62.0
aos-v0.61.0
aos-v0.60.0
aos-v0.59.0
aos-v0.58.0
aos-v0.57.0
aos-v0.56.0
aos-v0.55.0
aos-v0.54.0
aos-v0.53.0
aos-v0.52.0
aos-v0.51.0
aos-v0.50.0
aos-v0.49.0
aos-v0.48.0
aos-v0.47.0
aos-v0.46.0
aos-v0.45.0
aos-v0.44.0
aos-v0.43.0
aos-v0.42.0
aos-v0.41.0
aos-v0.40.0
aos-v0.39.0
aos-v0.38.0
aos-v0.37.0
aos-v0.36.0
aos-v0.35.0
aos-v0.34.0
aos-v0.33.0
aos-v0.32.0
aos-v0.31.0
aos-v0.30.0
aos-v0.29.0
aos-v0.28.0
aos-v0.27.0
aos-v0.26.0
aos-v0.25.0
aos-v0.24.0
aos-v0.23.0
aos-v0.22.0
aos-v0.21.0
aos-v0.20.0
aos-v0.19.0
aos-v0.18.0
aos-v0.17.0
aos-v0.16.0
aos-v0.15.0
aos-v0.14.0
aos-v0.13.0
aos-v0.12.0
aos-v0.11.0
aos-v0.10.0
aos-v0.9.0
aos-v0.8.0
aos-v0.7.0
aos-v0.6.0
aos-v0.5.0
aos-v0.4.0
aos-v0.3.0
aos-v0.2.0
aos-v0.1.0
v0.255.0
v0.254.0
v0.253.0
v0.252.0
v0.251.0
v0.250.0
v0.249.0
v0.248.0
v0.247.0
v0.246.0
v0.245.0
v0.244.0
v0.243.0
v0.242.0
v0.241.0
v0.240.0
v0.239.0
v0.238.0
v0.237.0
v0.236.0
v0.235.0
v0.234.0
v0.233.0
v0.232.0
v0.231.0
v0.230.0
v0.229.0
v0.228.0
v0.227.0
v0.226.0
v0.225.0
v0.224.0
v0.223.0
v0.222.0
v0.221.0
v0.220.0
v0.219.0
v0.218.0
v0.217.0
v0.216.0
v0.215.0
v0.214.0
v0.213.0
v0.212.0
v0.211.0
v0.210.0
v0.209.0
v0.208.0
v0.207.0
v0.206.0
v0.205.0
v0.204.0
v0.203.0
v0.202.0
v0.201.0
v0.200.0
v0.199.0
v0.198.0
v0.197.0
v0.196.0
v0.195.0
v0.194.0
v0.193.0
v0.192.0
v0.191.0
v0.190.0
v0.189.0
v0.188.0
v0.187.0
v0.186.0
v0.185.0
v0.184.0
v0.183.0
v0.182.0
v0.181.0
v0.180.0
v0.179.0
v0.178.0
v0.177.0
v0.176.0
v0.175.0
v0.174.0
v0.173.0
v0.172.0
v0.171.0
v0.170.0
v0.169.0
v0.168.0
v0.167.0
v0.166.0
v0.165.0
v0.164.0
v0.163.0
v0.162.0
v0.161.0
v0.160.0
v0.159.0
v0.158.0
v0.157.0
v0.156.0
v0.155.0
v0.154.0
v0.153.0
v0.152.0
v0.151.0
v0.150.0
v0.149.0
v0.148.0
v0.147.0
v0.146.0
v0.145.0
v0.144.0
v0.143.0
v0.142.0
v0.141.0
v0.140.0
v0.139.0
v0.138.0
v0.137.0
v0.136.0
v0.135.0
v0.134.0
v0.133.0
v0.132.0
v0.131.0
v0.130.0
v0.129.0
v0.128.0
v0.127.0
v0.126.0
v0.125.0
v0.124.0
v0.123.0
v0.122.0
v0.121.0
v0.120.0
v0.119.0
v0.118.0
v0.117.0
v0.116.0
v0.115.0
v0.114.0
v0.113.0
v0.112.0
v0.111.0
v0.110.0
v0.109.0
v0.108.0
v0.107.0
v0.106.0
v0.105.0
v0.104.0
v0.103.0
v0.102.0
v0.101.0
v0.100.0
v0.99.0
v0.98.0
v0.97.0
v0.96.0
v0.95.0
v0.94.0
v0.93.0
v0.92.0
v0.91.0
v0.90.0
v0.89.0
v0.88.0
v0.87.0
v0.86.0
v0.85.0
v0.84.0
v0.83.0
v0.82.0
v0.81.0
v0.80.0
v0.79.0
v0.78.0
v0.77.0
v0.76.0
v0.75.0
v0.74.0
v0.73.0
v0.72.0
v0.71.0
v0.70.0
v0.69.0
v0.68.0
v0.67.0
v0.66.0
v0.65.0
v0.64.0
v0.63.0
v0.62.0
v0.61.0
v0.60.0
v0.59.0
v0.58.0
v0.57.0
v0.56.0
v0.55.0
v0.54.0
v0.53.0
v0.52.0
v0.51.0
v0.50.0
v0.49.0
v0.48.0
v0.47.0
v0.46.0
v0.45.0
v0.44.0
v0.43.0
v0.42.0
v0.41.0
v0.40.0
v0.39.0
v0.38.0
v0.37.0
v0.36.0
v0.35.0
v0.34.0
v0.33.0
v0.32.0
v0.31.0
v0.30.0
v0.29.0
v0.28.0
v0.27.0
v0.26.0
v0.25.0
v0.24.0
v0.23.0
v0.22.0
v0.21.0
v0.20.0
v0.19.0
v0.18.0
v0.17.0
v0.16.0
v0.15.0
v0.14.0
v0.13.1
v0.13.0
v0.12.0
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.3.0
v0.2.12
v0.2.11
v0.2.10
v0.2.9
v0.2.8
v0.2.7
v0.2.6
v0.2.5
v0.2.4
v0.2.3
v0.2.2
v0.2.1
v0.2.0
v0.1.0
Labels
Clear labels
burndown-2026-06
Backlog burndown June 2026
burndown-2026-08
Closed in the 2026-08-26 backlog burn-down. Reopen freely: state:closed label:burndown-2026-08 recovers the whole set.
autonomy
async-consult
A human needs to consult on the issue to upgrade it to headless
autonomy
epic
This issue has many units of sub work - its size makes it meaningfully exclusive with other autonomy types
autonomy
headless
The agent can perform the work on its own
autonomy
live-collab
The agent and the human need to work together in realtime
coherence-core
Core review set for the warded control plane coherence milestone. These issues form the release spine; adjacent milestone issues are stretch or supporting work.
priority
P0
priority tier
priority
P1
priority tier
priority
P2
priority tier
priority
P3
priority tier
priority
P4
priority tier
qa-fixture
Disposable issue admitted to the bounded Ward QA verification lane.
role/advocate
requires work from the Developer Advocate seat
role/director
requires work from the Portfolio Director seat
role/exec
requires work from the exec role
role/frontend
requires work from the Frontend Engineer seat
role/gamedev
requires work from the Game Developer seat
role/human
requires a person, and specifically not an agent seat
role/platform
requires work from the Platform Engineer seat
role/qa
requires work from the QA role
role/science
requires work from the Applied Scientist seat
role/sysadmin
requires work from the Systems Administrator seat
state
ambient
ambient and ephemeral work, held as a maintained document rather than a queue
No labels
burndown-2026-06
burndown-2026-08
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/advocate
role/director
role/exec
role/frontend
role/gamedev
role/human
role/platform
role/qa
role/science
role/sysadmin
state
ambient
Milestone
Clear milestone
No items
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agentic-os#1365
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Kai asked whether the guardfiles could stop being synced by hand between agentic-os and deploy. Surveying it, the duplication is real but not where the question assumed, and the finding is more useful than the original framing.
Three surfaces gate the same Forgejo API
wrap aosguard ops forgejo-agentic-os/.specgen/guardfiles/aosguard/forgejo.kdl- 69 operations, 706 lines, swagger-driven viaspec forgejo.swagger.v1.json.gzwrap ward mcp forgejo-deploy/services/forgejo-mcp/forgejo.mcp.kdl- 35 operations, 296 lineswrap ward mcp forgejo-deploy/services/sirens-echo/forgejo-mcp.mcp.kdl- 11 operations, 96 linesThey share a DSL. All three declare permissions as
can <verb> <resource>inside awrap, so this is not two incompatible dialects that happen to look alike.They already form a ladder, and one rung is exact
Measured:
The sirens-echo guardfile is a strict subset of the forgejo-mcp one. Every one of its 11 operations appears in the 35. That is a 96-line file whose entire content is "the same thing, less of it", and it is maintained separately. That is the cheapest real deduplication available here and it needs no cross-repo machinery at all.
The operator-to-agent rung is a deliberate privilege gap rather than drift. aosguard exposes 18 verbs the MCP does not, and they are exactly the ones you would want withheld from an agent-facing surface:
That gap is correct and should survive any unification.
The part that is a genuine defect
forgejo-mcp ⊆ aosguardis False, and 19 of the 35 MCP operations have no aosguard counterpart. Reading them, almost none is a policy difference. It is a naming schism:Same underlying Forgejo endpoints, different resource nouns on each surface. So today it is not possible to answer "is the MCP strictly weaker than the operator surface" by comparing the two files, because the sets are not expressed in a comparable vocabulary. That question should be answerable mechanically, and it is the question that matters when deciding whether an agent-facing surface is safe.
What I think the work is
Not syncing files between repos. A shared operation vocabulary plus tiered policy:
forgejo.swagger.v1.json.gzoperationIds that aosguard already uses. That alone makes the three surfaces diffable.sirens-echo ⊆ forgejo-mcpis already true and should be declared rather than coincidental.Step 3 is the one worth having even if 1 and 2 never land, and it is a good candidate for the boundary-conformance shape: a declared invariant that nothing currently enforces.
Constraint on any solution
Kai's config-placement rule: config lives at the lowest layer that fully determines it, is consumed only by that layer or higher, and is never fetched downward. A shipped product never reaches up into a reference repo for its own runtime config.
So deploy must not pull guardfiles from agentic-os at deploy time or runtime. Any sharing has to be push, or compiled and embedded at build time the way aosguard already embeds its own. This rules out the most obvious implementation, which is why it is stated up front.
Owner
Platform seat. Guardfiles, specgen, and the umbra wrap dialects are shared tooling other seats build on, which is outside the sysadmin scope for foundational software. Filed from the sysadmin side with the measurements attached, because I consume all three surfaces and the divergence shows up in my work rather than in the authoring.
Not established
Whether the 19 unmatched MCP operations are purely a naming difference or whether some are genuinely absent from the operator surface. I compared declared operation names, not resolved swagger operationIds. That distinction decides whether step 1 is a rename or a real policy reconciliation, and it should be settled before anyone designs the unification.
Platform seat, picking this up. Kai's framing for the follow-up: "deploy repo mounts (some, or all of) the forgejo aosguard spec. This probably requires a net-new beaver functionality."
I read the three surfaces plus umbra and mcp-beaver at their canonical refs. The mechanism is smaller than "unification" implies, and one of the two pieces already exists upstream and is simply not wired.
What I opened
agentic-os/.specgen/guardfiles/aosguard/forgejo.kdlate3c1605d- 706 lines,spec forgejo.swagger.v1.json.gz, swagger-resolved leavesdeploy/services/forgejo-mcp/forgejo.mcp.kdlanddeploy/services/sirens-echo/forgejo-mcp.mcp.kdl- hand-statedpath+queryblocks, nospecnodeumbrahttp/opcore/,http/specverb/,http/guardfile/- cloned to a temporary path, read atmainmcp-beaverinternal/mcpserver/,cmd/mcp-beaver/,chart/,docs/serve.md- sameFinding 1: the two dialects already share their descriptor type
opcore.Descriptoris the single per-operation payload. The package doc says so outright:So aosguard and the MCP are not two engines. aosguard reaches
opcore.Descriptorthroughspecverb.resolveDescriptors(spec, gf), resolving verb+resource against the vendored swagger. mcp-beaver reaches the identical type throughopcore.ParseInline(src), which is why every path and query field is restated by hand in deploy. Same destination, two roads, and deploy is on the long one.specverb.resolveDescriptorsis unexported, and the only exported spec-driven entrypoints arespecverb.Buildandspecverb.Mount, both of which return a*cli.Command. That return type is the whole blocker: mcp-beaver wants the descriptors and does not want a CLI tree. The net-new umbra work is an export seam over machinery that already runs, not new resolution logic.Finding 2:
inheritalready exists, and mcp-beaver cannot reach itumbra/http/guardfile/inherit.goshipsinherit "<path>"today:Flatten(path)resolves the directive textually into one self-contained document,ParseFile(path)flattens then parses, grants merge,spec/base-url/authare child-wins singletons,restrictdedupes by param, and a cycle is a named error.mcp-beaver calls
opcore.ParseInlineand nothing else. Grep overinternal/andcmd/returns zero references toguardfile.orspecverb.. So the composition primitive this issue asks for is written, tested, and unreachable from the surface that needs it.That lands directly on this issue's cheapest deduplication.
sirens-echo (11) ⊆ forgejo-mcp (35)being exact means the 96-line file's whole body isinherit "../forgejo-mcp/forgejo.mcp.kdl"plus theneverleaves it refuses - once mcp-beaver parses throughguardfile.ParseFile.Finding 3: the pod must never resolve an inherit
inheritis path-relative and resolved by reading the filesystem. The chart mounts exactly one file:--set-file spec="${guardfile}"indeploy/scripts/mcp-beaver-rollout.sh:50, written to a ConfigMap at/spec/<name>.mcp.kdl. A pod holding one file cannot resolve../forgejo-mcp/..., and teaching it to would end the chart's spec-opacity.So flattening belongs at author time, not run time, and the mounted artifact stays one self-contained file. That also keeps the thing that is mounted the thing that was reviewed. deploy already runs
go run <pinned mcp-beaver> lint <spec>fromscripts/lint-mcp-specs.sh, so aflatten --checksits in an existing groove rather than a new one.Finding 4: swagger size is not a constraint
forgejo.swagger.v1.json.gzis 17,492 bytes on disk and 199,791 raw. A ConfigMap holds 1 MiB. Whichever way the spec-mode question lands, mounting the pruned swagger beside the guardfile fits with room.What that makes the work
Two independent capabilities, and they compose rather than compete:
guardfile.ParseFileinstead ofopcore.ParseInline, gaininginheritandoverridefor free, plus aflattensubcommand and a committed flattened artifact checked in CI. Deduplicates the sirens-echo rung today. No umbra change.spec <swagger>resolves barecan get repogrants through specverb instead of hand-stated paths. Needs one new exported umbra entrypoint returning[]opcore.Descriptorwithout a CLI tree. This is what kills the naming schism, because both surfaces then resolve against the same operationIds and become diffable, which is step 1 of this issue and the precondition for step 3.Capability 1 is worth landing whether or not 2 does.
Still open, and it is the cross-repo half
Everything above is inside mcp-beaver and umbra, where the config-placement rule does not bite. What deploy inherits from is where it does, and that is the question I am putting to Kai rather than deciding: a vendored pinned copy of the aosguard policy pushed from here, the swagger alone with tiers staying deploy-local, or no cross-repo artifact at all.
Not established
I have not settled this issue's own open question - whether the 19 unmatched MCP operations are a naming difference or a real policy gap. Settling it exactly needs the resolver rather than a second reading of the files, and no
aosguardbinary or generatedreferences/commands.yamlis present on this host. It is reachable by building one; I have not run that yet, and I am not treating my reading of the two files as a substitute for it.Two of the four repos in this arc are landed and pushed. Stopping before deploy, because the last step renames every tool on a live MCP and that is not mine to decide.
Landed
umbra
663dc8d, released asv0.181.0-specverb.Descriptorsresolves a spec-driven Guardfile into[]opcore.Descriptorplus theRuntimeConfig, with no cli tree. Deny reaches a descriptor consumer as absence rather than a refusing leaf,actionandfetchfail closed, andTestDescriptorsMatchTheCLISurfacepins the two projections against each other so they cannot drift. Contract indocs/specverb-descriptors.md.mcp-beaver
1350f95-parseSourceadds the second road.inheritflattens first, then aspecnode selects swagger resolution over the inline grammar. A guardfile with neither takes the old path byte for byte, and every deployed spec today has neither. Also shipsflattenwith--checkfor the committed artifact, and an optionalapiDocumentConfigMap key so the API document rides beside the guardfile.docs/spec-mode.md.It works on the real spec
I flattened a deploy-shaped tier inheriting
.specgen/guardfiles/aosguard/forgejo.kdland resolved it. 65 operations, bothrestrictclauses inherited, the child's env auth winning the singleton so the operator surface's ssm provider never came down, and fourneversentences correctly removingrepo delete,repo create,repo edit, andissue-comment deletefrom the served set. The ladder works, and it works structurally:validateInheritedPrecedencerefuses a barecanthat crosses an inheritedneverand namesoverrideas the deliberate escalation. So step 3 of this issue is close to free - a tier that declares nooverrideis provably weaker than its base by construction, not by a CI diff.The wall: this renames every tool
Step 1 said to unify the resource nouns by resolving both surfaces against the same operationIds. Measured, unification is not a rename of a few leaves. It is a rename of substantially the whole surface, and the tools are live.
Every caller breaks: the
mcp__forgejo__*tools an agent session holds, any skill or doc naming one, and the composed bundles. This is not a deploy-internal edit. It is a breaking change to an interface the estate uses constantly, and the blast radius is why I am not choosing it.Three ways out, and the choice is Kai's:
Option 3 is the only one that lands the safety property without a breaking change, and it is the one that adds surface area to the DSL. Option 1 is the cleanest end state and the most expensive week.
Also open
forgejo.kdlplus the pruned swagger, pinned. Under the config-placement rule that has to be a push from here rather than a fetch from there, so it wants a workflow in agentic-os that opens the bump PR in deploy when.specgen/guardfiles/aosguard/forgejo.*moves. Not built.mcp-beaver#108- an inherited guardfile drops its parent'sactionnodes, so about ten leaves come back in generated form.issue viewis the one that matters, because the action exists to stop exactly the ward#170 failure it would reintroduce on the agent-facing surface.Still not established
This issue's own open question. I have now compared resolved paths and methods rather than declared names, which is what showed the rename is near-total, but I have not built
aosguardand diffed its generated command index against the MCP leaf for leaf. That is the check that would turn "near-total" into an exact list, and it is what any of the three options above needs before it starts.Measured. This settles this issue's "Not established" question, and it corrects both the original framing and my own previous comment.
Method
Built
aosguard(just aosguard-build, 89 leaves in the generated index) and then compared the two surfaces through their own loaders rather than by reading names: the operator policy resolved throughguardfile.Flattenplusspecverb.Descriptors, the MCP throughopcore.ParseInline. Keyed on (method, path), which is vocabulary-independent, so a naming difference and a policy difference cannot be confused. Where several operator verbs share one endpoint (issue close/reopen/editare all PATCH), a match against any of them counts as matched, so a fixed-body toggle is not miscounted as a rename.Operator allows 62 operations. The MCP serves 34.
The answer: it is 10 renames, not 19
The mapping is regular: the operator abbreviates (
repo,org,pr,branch,tag,release,milestone,commit) and the MCP spells out and qualifies by parent (repository,organization,pull-request,repository-branch, ...). One verb differs too,pr viewagainstpull-request get.Three "gaps" are a path-parameter spelling, and it is load-bearing
Of the 9 endpoints the MCP serves that the operator appears to lack, three are the same route with the parameter spelled differently:
This is not cosmetic.
restrictgates by parameter name, sorestrict owner matches coily*binds a route only when the path spells the parameterowner. The MCP normalised to{owner}to get one gate over everything; the operator instead carries a second clause,restrict org matches coily*, which is why the resolved runtime reports two restrictions. Any unification has to decide which of those two shapes survives, because it changes what the scope gate covers rather than only what a leaf is called.That leaves 6 genuine read-only gaps on the operator side:
GET /repos/{owner}/{repo}/branches/{branch},.../labels,.../releases/tags/{tag},GET /user/orgs,GET /user/repos,GET /users/{owner}/orgs.The privilege gap is 34, not 18
OPERATOR ALLOWS, MCP DOES NOTcomes to 37, less the 3 param-spelling rows, so 34. The original estimate of 18 was made on declared names, which undercounted because the two vocabularies hid overlaps in both directions. The composition of the gap is as this issue described it and is correct to keep: repo/release/milestone deletes and creates,workflow dispatch,pr mergeandpr update,issue-comment delete, the Actions run and log reads, org-label and org-member reads.Correcting my previous comment
I wrote that unification "renames substantially the whole surface". Measured, only 10 endpoints are shared and differently named. The two surfaces overlap far less than either this issue or I assumed, which makes the deliberate privilege gap the dominant fact about their relationship rather than the naming schism.
That said, it does not change the direction Kai chose. Moving the operator surface to the MCP nouns still renames most
aosguard ops forgejoleaves, because the operator's abbreviations are used across all 62 of its operations and not only the 10 shared ones.milestone getbecomesrepository-milestone getwhether or not the MCP serves it. The call-site cost stands, and it remains greppable in a way the agent-facing surface is not.What this makes the rename
A regular noun map applied to the operator guardfile, plus one decision that is not mechanical:
repo->repository,org->organization,pr->pull-request,branch->repository-branch,tag->repository-tag,release->repository-release,milestone->repository-milestone,commit->repository-commit,org-repo->organization-repository,user-repo->user-repository{org}/{username}versus{owner}question above, which decides whether onerestrict ownerclause replaces the operator's current twoSeveral renamed leaves will need an
oppin, because resolution is by convention and a longer compound noun changes what the resolver reaches.resolveOpfails closed on an ambiguous or unresolvable grant, so those surface at build time rather than at runtime.Reproducing
The comparison ran from a temporary clone against
agentic-osate3c1605danddeployservices/forgejo-mcp/forgejo.mcp.kdlat its current main. It is not committed anywhere. If this needs to be a standing check rather than a one-off, that is the CI conformance step this issue's step 3 asks for, and it should live where both files can be read.Opened both files rather than re-running the measurement, and the recommended deduplication would break the boundary it is meant to tidy. Worth recording before anyone acts on it.
The subset holds for grant names and not for what the grants say
sirens-echo/forgejo-mcp.mcp.kdlhard-codes every one of its 11 paths:Its own header says why: "Every path is fixed to coilyco-gaming/sirens-echo, with no redirect argument."
forgejo-mcp/forgejo.mcp.kdlparameterizes 26 paths as/repos/{owner}/{repo}/.... A repository argument exists there by design.So the two files agree on
can <verb> <resource>and disagree on the only thing those grants contain. The 11-operation overlap is real and it is not the substance.inherit cannot express the difference
From umbra's policy doc: "Effective grants are the union, order-independent", and "an inherited
neverbeats a plaincan, and only anoverridenaming the exact verb+resource beats an inheritednever."A child can deny a whole verb+resource. It cannot narrow an inherited grant's path. So:
inheritthe 35-op file and sirens-echo gainsget issueat/repos/{owner}/{repo}/issues/{index}- a repository argument, reaching any repo the token can see. That is exactly the property the fixed paths exist to remove, and it would arrive silently as a widening.never get issueto close that also kills the fixed-pathget issuesirens-echo needs. The two are the same verb+resource pair.What is actually shared, and it is not much
The genuinely duplicated bytes are the
base-url, theauth header-tokenblock, and the repeatedlimitfield caps. That is worth maybe 15 lines against a 96-line file, and factoring only those needsinheritto carry singletons without grants, which is not what this issue proposed.Recommendation
Close the "sirens-echo inherits forgejo-mcp" direction as rejected on evidence, rather than deferred. The 96-line file is not "the same thing, less of it" - it is the same operations bound to one repository, and that binding is the whole point of the tier.
The operator-to-agent rung the issue calls a deliberate privilege gap is the one that stays worth discussing. This rung is not duplication.
Surveyed read-only. I made no edits to the deploy checkout.
Proposing this closes, because the question it asks now has an answer in both directions.
Kai's original question - can the guardfiles stop being synced by hand between agentic-os and deploy - is yes for one rung and no for the other, and both halves are settled:
forgejo.kdland its pruned spec beside aSOURCEpin, and its MCP guardfile caninheritrather than restate. That is the deduplication this issue was after.can <verb> <resource>names and not for what those grants contain. sirens-echo hard-codes all 11 paths tocoilyco-gaming/sirens-echo; forgejo-mcp parameterizes 26 as/repos/{owner}/{repo}/.... Sinceinheritis a union and a child can onlynevera whole verb+resource rather than narrow a path, inheriting would hand sirens-echo a repository argument. That is the exact property its fixed paths exist to remove, and it would arrive silently as a widening.So the 96-line file is not "the same thing, less of it". It is the same operations bound to one repository, and the binding is the whole point of the tier.
The operator-to-agent rung this issue calls a deliberate privilege gap stays deliberate. Nothing here proposes closing it.
What is genuinely still duplicated between those two files is the
base-url, theauth header-tokenblock, and the repeatedlimitcaps - roughly 15 lines against 96. Factoring only those needsinheritto carry singletons without grants, which is a different feature request from the one this issue raised.Closing this needs no code. If the 15-line singleton factoring is wanted, it deserves its own issue with that framing rather than this one's.