permissions: make the Claude `settings.json` import reviewable — generate one file, in one lane, with every demotion visible
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 152
- Forks
- 16
- Avg merge
- 14h 48m
- Merged PRs (30d)
- 536
Description
permissions: make the Claude settings.json import reviewable — generate one file, in one lane, with every demotion visible
Sub-issue of the portability epic (#410) — see the parent for the shared artefact map and inherited
boundaries, including the rule this issue turns on: a *.local source never contributes to a
shared, checked-in destination.
Follow-up to #13 (--import-claude-permissions). Depends on sub-issue A only for where the
result is surfaced.
Problem
--import-claude-permissions translates Claude Code permission rules at runtime and is
lossy by design, fail-safe: a WebFetch(domain:…) ALLOW is demoted to ask, a
Read(~/…) rule is left inert with ~ unexpanded, an unparseable spec is dropped
(cmd/mecated/main.go:1491). Each case is logged — and a log line is not a review artefact.
The team that owns policy cannot sign off on a translation that exists only in a process's
stderr, and the developer cannot see which of their allows quietly became asks.
Why the obvious fix is wrong
"Generate .mecatl/settings.yaml from the Claude file" creates two live sources of truth:
permconfig resolves .claude/settings{,.local}.json and .mecatl/settings{,.local}.yaml
with precedence (resolve.go:29-43). The generated
file would silently win, then go stale the moment anyone edits the Claude side. Worse,
settings.local.json is personal and gitignored — folding it into a checked-in shared
settings.yaml leaks one developer's local allows to the whole repo.
Proposal — one lane, chosen explicitly
mecated migrate --emit-permissions writes one artefact, and the two candidate lanes are
mutually exclusive; pick one in review:
- (a)
.mecatl/settings.local.yaml— the personal, gitignored, higher-precedence lane.
Correct destination for asettings.local.jsontranslation; becomes live immediately, so
the runtime import flag should then be redundant for that scope. - (b)
.mecatl/settings.yaml.proposed(not loaded bypermconfig) — a review artefact for
the shared/checked-in lane. Nothing changes behaviour until a human renames it, which is the
point: policy lands through a PR.
Preference: (b) for the shared file, (a) for the local file — matching the trust scope of
each source. Never emit both lanes from one source file, and never emit a shared file from a
*.local.json source.
Whichever lane, the generated file carries an inline comment per translated rule:
# from .claude/settings.json: allow WebFetch(domain:example.com)
# DEMOTED to ask — domain-scoped WebFetch allows are not representable (fail-safe)
- match: "WebFetch"
mode: ask
# from .claude/settings.json: allow Read(~/notes/**)
# INERT — "~" is not expanded; rewrite with an absolute path to activate
Acceptance
- Emitting from a
.claude/settings.jsonwith each lossy class produces a file where every
rule is traceable to its source rule and every demotion/inert/dropped case is commented with
its reason. - A
settings.local.jsonsource never contributes to a shared/checked-in destination
(asserted by test). - The emitted artefact and the runtime
--import-claude-permissionspath produce the same
effective decisions for the same input — a test drives both and compares, so the visible
file cannot lie about behaviour. - Emit refuses to overwrite an existing destination (ADR 0099's no-overwrite discipline).
- Project ALLOW rules in the emitted file still require
--trust-projectto take effect; the
emit step grants nothing. - No secret, env value, or token is copied into the artefact.
- Docs:
docs/usage/permissions-config.mdgains the emit flow and the lane rule.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the import path at cmd/mecated/main.go:1491 and the destination precedence in internal/adapter/permconfig/resolve.go. Decide the explicit output lane, then trace each lossy rule through emission and runtime import so both paths produce the same effective decisions. Done means the acceptance cases pass, overwrite is refused, and docs/usage/permissions-config.md describes the flow and lane rule.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authorization, cli, documentation
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100