Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c524910
feat(permissions): resolve a named permission-mode token in composition
aponcedeleonch Sep 25, 2026
e7392db
test(permissions): pin the permission-mode contract in composition
aponcedeleonch Sep 25, 2026
3280616
feat(permissions): add --permission-mode to the server roots
aponcedeleonch Sep 25, 2026
e68fb44
fix(permissions): let gRPC CreateSession use the server default mode
aponcedeleonch Sep 25, 2026
37f9f3d
feat(mecatui): add --permission-mode and show the posture honestly
aponcedeleonch Sep 25, 2026
d78fed1
docs(permissions): document the permission-mode vocabulary
aponcedeleonch Sep 25, 2026
fab96fe
fix(permissions): let the startup fast paths see permissionMode:
aponcedeleonch Sep 25, 2026
fdb367e
refactor(permissions): name the checker states and bound loadProjectR…
aponcedeleonch Sep 25, 2026
4a6ab1d
docs(reference): regenerate the gRPC schema for the CreateSession mode
aponcedeleonch Sep 25, 2026
92e6c92
refactor(permissions): follow the ADR renumber to 0375
aponcedeleonch Oct 5, 2026
d95590e
test(permissions): declare the checker choice at the remaining allow-…
aponcedeleonch Oct 5, 2026
1a2e0be
test(permissions): declare the checker choice at allow-all sites adde…
aponcedeleonch Oct 8, 2026
d3fd50d
refactor(mecatequi): extract explicit-flag recording from parseFlags
aponcedeleonch Oct 8, 2026
4ed1273
chore(permissions): drop references to the deleted ADR 0375
aponcedeleonch Oct 8, 2026
8dace64
feat(mecatui): cycle every permission mode and block ones that need a…
aponcedeleonch Oct 8, 2026
1c4ad94
test(permissions): declare the checker choice at allow-all sites adde…
aponcedeleonch Oct 9, 2026
a06c2f9
test(permissions): adapt tests added on main to the permission-mode c…
aponcedeleonch Oct 14, 2026
fbf4644
refactor(permissions): make allow-all and loose-substitution mode-driven
aponcedeleonch Oct 14, 2026
235353b
fix(mecatui): wire auto/yolo modes end-to-end in the UI
aponcedeleonch Oct 14, 2026
bbf2d64
fix(mecatui): correct auto/yolo mode display and remove stale 'pendin…
aponcedeleonch Oct 14, 2026
e35b0c1
fix(lint,api,docs): fix gci ordering, unused receiver, goconst, api s…
aponcedeleonch Oct 14, 2026
ec32065
fix(permissions): make auto/yolo modes work end to end and split trus…
aponcedeleonch Oct 15, 2026
2228fc6
fix(sdk): project ServerCapabilities.projectTrusted in the TypeScript…
aponcedeleonch Oct 15, 2026
2af72e4
fix(permissions): address review on session-scoped permission modes
aponcedeleonch Oct 16, 2026
fcb4825
fix(ci): run kind fixtures in yolo and drop stale --guardrails base arg
aponcedeleonch Oct 16, 2026
43b28c1
test(e2e): create kind e2e sessions in the deployment default mode
aponcedeleonch Oct 16, 2026
90fd7ce
style(e2e): gofmt the live qualification create body
aponcedeleonch Oct 16, 2026
d89d066
fix(permissions): admit auto only when an enforcing guardrail rule co…
aponcedeleonch Oct 16, 2026
d8166f2
fix(permissions): derive legacy mode and ceiling after the full alias…
aponcedeleonch Oct 16, 2026
7978c30
fix(permissions): re-admit the stored mode under the acquired lease
aponcedeleonch Oct 16, 2026
c04bba2
docs(permissions): describe rollback for sessions stored in auto or yolo
aponcedeleonch Oct 16, 2026
7c8d873
fix(mecatui): gate session-scoped mode semantics on session_permissio…
aponcedeleonch Oct 16, 2026
7ffbc2f
fix(studio): report auto and yolo sessions instead of showing them as…
aponcedeleonch Oct 16, 2026
c5c37d2
fix(helm): reject single-dash mode flags in extraArgs
aponcedeleonch Oct 16, 2026
1a471d1
fix(helm): enumerate permissionMode values in the chart schema
aponcedeleonch Oct 16, 2026
1e35027
docs(ci): match the reusable mecatequi reviewer note to the action de…
aponcedeleonch Oct 16, 2026
cbb41d1
fix(permissions): keep explicit default child modes and re-admit resu…
aponcedeleonch Oct 16, 2026
a6cf4f1
fix(permissions): select checker enforcement from the executing child…
aponcedeleonch Oct 16, 2026
2de26a4
feat(permissions): recover a refused-mode awaiting session with a Set…
aponcedeleonch Oct 16, 2026
8f513e4
fix(permissions): refuse settings permissionMode with deprecated --po…
aponcedeleonch Oct 16, 2026
7b45c22
test(permissions): execute a yolo, auto, default sequence on one session
aponcedeleonch Oct 16, 2026
a6cbf1a
fix(permissions): adopt the under-lease snapshot at run entry
aponcedeleonch Oct 16, 2026
f7bca08
test(permissions): give the Team checker fixture a well-formed prohib…
aponcedeleonch Oct 16, 2026
dea7609
fix(schedule): fire a mutating in-chat schedule in its creator's mode
aponcedeleonch Oct 16, 2026
9689c6b
fix(schedule): keep an unspecified schedule mode as the server default
aponcedeleonch Oct 16, 2026
aaba681
fix(studio): show and preserve auto, yolo, and server-default schedul…
aponcedeleonch Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 55 additions & 14 deletions .github/actions/mecatequi/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
# version — there is no version input (matlatl has none either).
#
# OMITTED INPUT — the ask-reviewer policy file. --subagent-ask-reviewer is surfaced (see
# the input) so a posture:auto run can adjudicate a child's read-only substitution instead
# the input) so an auto-mode run can adjudicate a child's read-only substitution instead
# of silently auto-denying it, but --subagent-ask-reviewer-policy (a TRUSTED rubric file
# whose CONTENT replaces the built-in rubric) is NOT surfaced: it grants the reviewer a
# rule set the operator would have to author and check into the repo, which is a deliberate
Expand Down Expand Up @@ -124,13 +124,26 @@ inputs:
description: Session workspace root (must be a git repository so the diff can be computed).
required: false
default: ${{ github.workspace }}
permission-mode:
description: >-
Permission mode → --permission-mode: one of plan, default, accept-edits, auto,
yolo. Empty (the input default) resolves to "auto", the documented CI mode
(allow-all, child injection-defence ON), unless the deprecated `posture` input is
set. auto refuses to start without an ENFORCING guardrails checker, inherited from
operator settings or named with `guardrails-model`. yolo is the explicit choice to
run allow-all with no checker. "default" would CANCEL the run on the first mutate
because a headless run has no approver. Setting both this and `posture` fails the
step.
required: false
default: ""
posture:
description: >-
Operator posture ladder (strict < trusted < auto < yolo). Default "auto" — the
documented CI posture (allow-all, child injection-defence ON). "strict" would
CANCEL the run on the first mutate because a headless run has no approver.
DEPRECATED alias for `permission-mode`, kept for one release → --posture (the
binary WARNs). Used only when set non-empty; setting it together with
`permission-mode` fails the step. Migrate `posture: "auto"` to
`permission-mode: "auto"`.
required: false
default: "auto"
default: ""
timeout:
description: >-
Wall-clock bound on the whole run (e.g. 40m); the primary cost ceiling for a
Expand Down Expand Up @@ -185,17 +198,25 @@ inputs:
guardrails-model:
description: >-
GUARDRAILS (issue #27) checker model id / alias inspecting outbound args + inbound
results. Empty disables guardrails.
results. Empty (default) passes no guardrails flag, so the binary uses the checker
configured in operator settings (a `guardrails:` model or the `guardrail` model
slot), if any. The literal "off" passes --guardrails off, the kill-switch, which
also disables a checker configured in settings. Any other value passes
--guardrails-model <value>. The auto permission mode refuses to start without an
enforcing checker, so pair "off" or an unconfigured empty value with
`permission-mode: yolo`.
required: false
default: ""
subagent-ask-reviewer:
description: >-
OPT-IN headless ask reviewer (issue #31) → --subagent-ask-reviewer: model id / alias
of a tool-less one-turn reviewer that adjudicates a CHILD permission ask the headless
auto-deny would otherwise reject (so a posture:auto run whose subagent wants a
read-only $(...) substitution is reviewed rather than silently denied). Empty (default)
= no reviewer (children auto-deny). The TRUSTED policy-rubric file is NOT an input —
see the action header.
Headless ask reviewer (issue #31) → --subagent-ask-reviewer: model id / alias of a
tool-less one-turn reviewer that adjudicates a CHILD permission ask the headless
auto-deny would otherwise reject (so an auto-mode run whose subagent wants a
read-only $(...) substitution is reviewed rather than silently denied). Empty
(default) omits the flag: in the auto and yolo modes the reviewer is then ON by
default (ask-reviewer model slot, else the session model); in other modes children
auto-deny. "off" disables it explicitly. The TRUSTED policy-rubric file is NOT an
input — see the action header.
required: false
default: ""
subagent-ask-reviewer-max-denies:
Expand Down Expand Up @@ -365,6 +386,7 @@ runs:
MQ_UNTRUSTED: ${{ inputs.untrusted }}
MQ_INSTRUCTIONS: ${{ inputs.instructions }}
MQ_WORKSPACE: ${{ inputs.workspace }}
MQ_PERMISSION_MODE: ${{ inputs.permission-mode }}
MQ_POSTURE: ${{ inputs.posture }}
MQ_TIMEOUT: ${{ inputs.timeout }}
MQ_MAX_RUN_TOKENS: ${{ inputs.max-run-tokens }}
Expand Down Expand Up @@ -407,10 +429,23 @@ runs:
# Build the argv as an ARRAY from env values — never string-interpolated. Each
# event-derived value crosses as a discrete token, so no value can be re-parsed
# as a flag or a shell metacharacter.
# Permission mode: `permission-mode` replaces the deprecated `posture` input. Both
# set is ambiguous, so fail rather than guess; neither set is the documented CI mode.
if [ -n "${MQ_PERMISSION_MODE}" ] && [ -n "${MQ_POSTURE}" ]; then
echo "::error::both permission-mode and the deprecated posture input are set; pass only permission-mode."
exit 1
fi
if [ -n "${MQ_POSTURE}" ]; then
echo "::warning::the posture input is deprecated; use permission-mode instead."
permission_args=( --posture "${MQ_POSTURE}" )
else
permission_args=( --permission-mode "${MQ_PERMISSION_MODE:-auto}" )
fi

args=(
--prompt-file "${MQ_PROMPT_FILE}"
--workspace "${MQ_WORKSPACE}"
--posture "${MQ_POSTURE}"
"${permission_args[@]}"
--timeout "${MQ_TIMEOUT}"
--headless=true
--out-diff "${MQ_OUT_DIFF}"
Expand All @@ -426,7 +461,13 @@ runs:
[ -n "${MQ_DEFAULT_MODEL}" ] && args+=( --default-model "${MQ_DEFAULT_MODEL}" )
[ "${openai_on}" = "true" ] && args+=( --openai )
[ -n "${MQ_OPENAI_BASE_URL}" ] && args+=( --openai-base-url "${MQ_OPENAI_BASE_URL}" )
[ -n "${MQ_GUARDRAILS_MODEL}" ] && args+=( --guardrails-model "${MQ_GUARDRAILS_MODEL}" )
# Checker choice: empty inherits the operator-settings checker (no flag), "off"
# is the explicit kill-switch, anything else names the checker model.
if [ "${MQ_GUARDRAILS_MODEL}" = "off" ]; then
args+=( --guardrails off )
elif [ -n "${MQ_GUARDRAILS_MODEL}" ]; then
args+=( --guardrails-model "${MQ_GUARDRAILS_MODEL}" )
fi
[ -n "${MQ_ASK_REVIEWER}" ] && args+=( --subagent-ask-reviewer "${MQ_ASK_REVIEWER}" )
[ -n "${MQ_ASK_REVIEWER_MAX_DENIES}" ] && args+=( --subagent-ask-reviewer-max-denies "${MQ_ASK_REVIEWER_MAX_DENIES}" )

Expand Down
20 changes: 14 additions & 6 deletions .github/workflows/mecatequi-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ on:
# Inputs are the per-session passthrough surface of the mecatequi action. The
# escape-hatch-only knobs are deliberately NOT exposed here — `default-model` (catalog-
# validated, rejects an uncatalogued id — use `model` instead), `openai` (provider is
# auto-detected from the present key), and `subagent-ask-reviewer` (an autonomous-approval
# capability that is a deployment decision better made by editing the action) — so a
# consumer needing them vendors the escape-hatch template rather than the reusable workflow.
# auto-detected from the present key), and `subagent-ask-reviewer` (the action default
# applies: under its default auto permission mode the reviewer is ON, using the ask-reviewer
# model slot or else the session model; choosing another reviewer or "off" is a deployment
# decision better made by editing the action) — so a consumer needing them vendors the
# escape-hatch template rather than the reusable workflow.
inputs:
label:
description: >-
Expand Down Expand Up @@ -47,8 +49,11 @@ on:
default: ""
posture:
description: >-
Operator posture ladder (strict < trusted < auto < yolo). Default "auto" — the
documented CI posture (allow-all, child injection-defence ON).
Passed to the action's DEPRECATED `posture` input (the binary WARNs): strict,
trusted, auto, or yolo. Default "auto" resolves to permission mode auto (allow-all,
child injection-defence ON), which refuses to start without an ENFORCING
guardrails checker; use "yolo" to run allow-all with no checker. It never grants
project trust.
type: string
required: false
default: auto
Expand Down Expand Up @@ -84,7 +89,10 @@ on:
guardrails-model:
description: >-
Guardrails (issue #27) checker model id / alias that inspects outbound tool args +
inbound results. Empty (default) disables guardrails.
inbound results. Empty (default) passes no guardrails flag, so the checker
configured in operator settings applies, if any. "off" is the kill-switch. Any
other value names the checker model. With posture "auto", an empty value and no
settings checker, or "off", refuses to start.
type: string
required: false
default: ""
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/mecatequi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# mecatequi — the LIVE workflow for THIS repo (mecatl). Private repo, internal posture.
#
# To run it: label an issue `mecatequi`, OR comment `@mecatequi` on an issue. The agent
# runs a single-shot mecatequi pass over the issue text (untrusted, posture:auto) against
# runs a single-shot mecatequi pass over the issue text (untrusted, permission-mode auto) against
# OpenRouter, and the publish job opens a PR (or posts an honest comment) with the result.
#
# Unlike .github/workflows/mecatequi-example.yml (the inert template), this file is REAL:
Expand Down Expand Up @@ -221,7 +221,10 @@ jobs:
with:
prompt-file: ${{ steps.prompt.outputs.prompt-file }}
untrusted: "true"
posture: "auto"
# yolo: a deliberate unchecked choice for this private, split-privilege CI
# run. auto would refuse to start because no guardrails checker is configured.
permission-mode: "yolo"
guardrails-model: "off"
# Use `model` (per-session passthrough), NOT `default-model`: a deployment
# default is validated against the embedded models.dev catalog at startup, and
# claude-sonnet-4.6 is newer than that snapshot (it would fail "not catalogued").
Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ tasks:
set -eu -o pipefail
missing=""
rendered=$(helm template production deploy/helm/mecak8s --set '{{.SAFE_SET}}')
for arg in --grpc-addr --http-addr --redis-url --session-lease-k8s-namespace --headless --posture --oidc-issuer --oidc-audience --oidc-max-jwks-staleness=1h; do
for arg in --grpc-addr --http-addr --redis-url --session-lease-k8s-namespace --headless --permission-mode --oidc-issuer --oidc-audience --oidc-max-jwks-staleness=1h; do
echo "$rendered" | grep -q -- "$arg" || missing="$missing $arg"
done
if [ -n "$missing" ]; then
Expand Down
26 changes: 19 additions & 7 deletions apps/contracts/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1814,7 +1814,9 @@
"enum": [
"default",
"plan",
"acceptEdits"
"acceptEdits",
"auto",
"yolo"
]
},
"model": {
Expand Down Expand Up @@ -2991,7 +2993,9 @@
"enum": [
"default",
"plan",
"acceptEdits"
"acceptEdits",
"auto",
"yolo"
]
}
},
Expand Down Expand Up @@ -5527,7 +5531,10 @@
"enum": [
"default",
"plan",
"acceptEdits"
"acceptEdits",
"auto",
"yolo",
"serverDefault"
]
},
"modelId": {
Expand Down Expand Up @@ -5882,7 +5889,10 @@
"enum": [
"default",
"plan",
"acceptEdits"
"acceptEdits",
"auto",
"yolo",
"serverDefault"
]
},
"modelId": {
Expand Down Expand Up @@ -6190,8 +6200,7 @@
"default",
"plan",
"acceptEdits"
],
"default": "plan"
]
},
"mutating": {
"type": "boolean",
Expand Down Expand Up @@ -6306,7 +6315,10 @@
"enum": [
"default",
"plan",
"acceptEdits"
"acceptEdits",
"auto",
"yolo",
"serverDefault"
]
},
"modelId": {
Expand Down
10 changes: 5 additions & 5 deletions apps/contracts/src/generated/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ export type GetSessionDetailResponses = {
};
id: string;
kind: string;
mode: 'default' | 'plan' | 'acceptEdits';
mode: 'default' | 'plan' | 'acceptEdits' | 'auto' | 'yolo';
model?: {
id: string;
providerId: string;
Expand Down Expand Up @@ -1033,7 +1033,7 @@ export type SetSessionModeResponses = {
* The authoritative session permission mode.
*/
200: {
mode: 'default' | 'plan' | 'acceptEdits';
mode: 'default' | 'plan' | 'acceptEdits' | 'auto' | 'yolo';
};
};

Expand Down Expand Up @@ -1854,7 +1854,7 @@ export type ListSchedulesResponses = {
lastFireAt: string;
lastFireSessionId: string;
maxFires: number;
mode: 'default' | 'plan' | 'acceptEdits';
mode: 'default' | 'plan' | 'acceptEdits' | 'auto' | 'yolo' | 'serverDefault';
modelId: string;
mutating: boolean;
name: string;
Expand Down Expand Up @@ -1965,7 +1965,7 @@ export type CreateScheduleResponses = {
lastFireAt: string;
lastFireSessionId: string;
maxFires: number;
mode: 'default' | 'plan' | 'acceptEdits';
mode: 'default' | 'plan' | 'acceptEdits' | 'auto' | 'yolo' | 'serverDefault';
modelId: string;
mutating: boolean;
name: string;
Expand Down Expand Up @@ -2141,7 +2141,7 @@ export type UpdateScheduleResponses = {
lastFireAt: string;
lastFireSessionId: string;
maxFires: number;
mode: 'default' | 'plan' | 'acceptEdits';
mode: 'default' | 'plan' | 'acceptEdits' | 'auto' | 'yolo' | 'serverDefault';
modelId: string;
mutating: boolean;
name: string;
Expand Down
10 changes: 8 additions & 2 deletions apps/contracts/src/schemas/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { z } from "zod";

export const sessionModeSchema = z.enum(["default", "plan", "acceptEdits"]);

/**
* A session's reported mode. It adds the autonomous modes a session created
* outside Studio can run in; Studio reads them but never requests them.
*/
export const sessionReportedModeSchema = z.enum(["default", "plan", "acceptEdits", "auto", "yolo"]);

export const sessionToolAccessSchema = z.enum(["all", "noFilesystem"]);

export const reasoningEffortSchema = z.enum(["default", "low", "medium", "high", "xhigh", "max"]);
Expand Down Expand Up @@ -123,7 +129,7 @@ export const sessionDetailResponseSchema = z.object({
}),
id: z.string(),
kind: z.string(),
mode: sessionModeSchema,
mode: sessionReportedModeSchema,
model: sessionModelSelectionSchema
.extend({
contextWindow: z.string(),
Expand Down Expand Up @@ -155,7 +161,7 @@ export const setSessionModeRequestSchema = z.object({
});

export const setSessionModeResponseSchema = z.object({
mode: sessionModeSchema,
mode: sessionReportedModeSchema,
});

export const forkSessionRequestSchema = z.object({
Expand Down
26 changes: 24 additions & 2 deletions apps/contracts/src/schemas/schedules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ export const maximumInt32 = 2_147_483_647;

export const scheduleModeSchema = z.enum(["default", "plan", "acceptEdits"]);

/**
* A schedule's reported mode. It adds what a schedule created outside Studio
* can run in: `auto` and `yolo` (an in-chat schedule records its creating
* session's mode), and `serverDefault` (no mode was set, so each fire uses the
* server's default session mode). Studio reads these but never requests them.
*/
export const scheduleReportedModeSchema = z.enum([
"default",
"plan",
"acceptEdits",
"auto",
"yolo",
"serverDefault",
]);

/** Each segment of an IANA zone name starts with an uppercase letter. */
const ianaZoneName = /^[A-Z][A-Za-z0-9_+-]*(?:\/[A-Z][A-Za-z0-9_+-]*)*$/u;

Expand Down Expand Up @@ -72,15 +87,22 @@ export const createScheduleRequestSchema = scheduleWriteSchema.extend({
name: z.string().trim().min(1).max(160),
});

export const updateScheduleRequestSchema = scheduleWriteSchema;
/**
* An update may omit `mode` to keep the stored one, which is how a writing
* schedule whose mode Studio cannot set (`auto`, `yolo`, `serverDefault`) is
* edited without changing it. A sent mode is still one Studio can create.
*/
export const updateScheduleRequestSchema = scheduleWriteSchema.extend({
mode: scheduleModeSchema.optional(),
});

export const scheduleSchema = z.object({
enabled: z.boolean(),
fireCount: z.number().int().nonnegative(),
lastFireAt: z.string().nullable(),
lastFireSessionId: z.string(),
maxFires: z.number().int().nonnegative(),
mode: scheduleModeSchema,
mode: scheduleReportedModeSchema,
modelId: z.string(),
mutating: z.boolean(),
name: z.string(),
Expand Down
Loading
Loading