t1k:sync-back
| Field | Value |
|---|---|
| Module | t1k-kit-feedback |
| Version | 1.2.2 |
| Effort | high |
| Tools | AskUserQuestion, Bash, Edit, Glob, Grep, Read, Skill, Task, Write |
Keywords: contribute, gotcha, pr, propagate, push, sync, upstream
How to invoke
Section titled “How to invoke”/t1k:sync-back[<skill-name>|<file-path>] [--dry-run|--force|--already-pushed=pr=N]TheOneKit Sync-Back — Push Changes to Kit Repos
Section titled “TheOneKit Sync-Back — Push Changes to Kit Repos”Push $HOME/.claude/ changes (skills, agents, rules) back to their origin kit repos as PRs.
Uses GitHub MCP tools — no local clone needed.
This skill is the canonical mechanism for the kit-wide-fix discipline. See
rules/kit-wide-fix-discipline.md— local-only edits regress on nextt1k modules update; sync-back closes the loop by opening a kit-source PR.
/t1k:sync-back # NO ARGS = sync EVERYTHING (all unsynced files under both scan roots)/t1k:sync-back <skill-name> # Scope to one skill, e.g. /t1k:sync-back t1k-unity-docs/t1k:sync-back <file-path> # Scope to one file (relative to a scan root)/t1k:sync-back <target> --dry-run # Plan + diff only, no PR/t1k:sync-back --force # Skip confirmation. Diff is ALWAYS shown./t1k:sync-back <target> --already-pushed=pr=N # Branch+PR already exist (manual or prior sync); # skip clone/push/PR-create; verify + record telemetry only.No-args mode = sync everything (MANDATORY default)
Section titled “No-args mode = sync everything (MANDATORY default)”When invoked with no positional arg and no flags:
- Always run the full scan. Walk
$HOME/.claude/AND<cwd>/.claude/perreferences/routing-and-paths.mdStep 0. Do NOT short-circuit on an empty lesson queue — the queue is one input among many, never the only one. - Sync every candidate found. Group by origin kit/repo per
references/routing-and-paths.mdStep 3 and open one PR per repo, multi-skill if needed. - Never ask “what task did you mean?” A no-args invocation is an explicit instruction to sync the entire pending set. Treat it the same way
git status && git add -A && git committreats no args — process everything. - If the full scan finds zero candidates, exit cleanly with:
This is success, not a prompt for input.No pending changes to sync. Scanned: $HOME/.claude/ and <cwd>/.claude/. Lesson queue: empty. Working tree: clean vs origin.
- Anti-pattern: the sub-agent fork seeing
/t1k:sync-backwith no args and replying “I’ll wait for your actual task” — this happened in the May 2026 sessions. The skill MUST proceed with the full scan; clarification questions are forbidden in no-args mode.
Argument parsing (when an arg IS given)
Section titled “Argument parsing (when an arg IS given)”The first non-flag token is the target. Resolution order:
- Skill name — if
$HOME/.claude/skills/<token>/OR<cwd>/.claude/skills/<token>/exists, sync everything under that directory only. - File path — if the token resolves to a file under a scan root (with or without the
$HOME/.claude/prefix), sync just that file. - No match → HARD-FAIL:
"Target '<token>' is neither a skill name nor a file under $HOME/.claude/ or $(pwd)/.claude/. Did you mean: <5 nearest skill names>?"Do NOT silently fall back to full-tree discovery — that mis-reports scope and surprises the user.
Tokens starting with - are flags, never targets.
Sub-agent context loss: sync-back invoked from a sub-agent fork loses the parent’s conversation context. The skill MUST work from arg + filesystem state alone — never assume conversation memory. No-args = scan everything; explicit-target = scope to that target. Both modes are self-contained.
Scope — Consumer Projects Only (MANDATORY)
Section titled “Scope — Consumer Projects Only (MANDATORY)”Do NOT invoke when CWD is a kit source repo (theonekit-core, theonekit-unity, theonekit-cli, theonekit-designer, theonekit-cocos, theonekit-rn, theonekit-web, theonekit-nakama, theonekit-release-action, t1k-telemetry-worker). Those repos ARE the origin — commit directly. See references/consumer-guard.md.
Invocation Mode (MANDATORY — Background Sub-Agent)
Section titled “Invocation Mode (MANDATORY — Background Sub-Agent)”This skill MUST run as a background sub-agent via the Task tool, NEVER inline.
Exception: explicit user request (“sync this now”) → run inline so the diff is visible.
See references/sub-agent-invocation.md for the required Task call template, required fields (kitVersion, moduleVersion, cliVersion, platform, rationale), and the auto-lesson writeback protocol (submitted: true, prUrl, fingerprint).
Transport: GitHub MCP first, gh CLI fallback (sandbox-asymmetry-safe)
Section titled “Transport: GitHub MCP first, gh CLI fallback (sandbox-asymmetry-safe)”Parent context having access to GitHub MCP or gh does NOT guarantee the spawned sub-agent has them — sandbox/permission scopes are evaluated per Task invocation. Probe BOTH at sub-agent start:
- Preferred — GitHub MCP: if
mcp__github__*tools are listed in the sub-agent’s tool roster, use them (seereferences/open-pr.mdmain flow). - Fallback —
ghCLI (when GitHub MCP is absent):gh auth statusmust return 0 AND the sub-agent’s Bash permissions must allowgh api,gh pr create, and eithergh repo cloneor rawgitagainsthttps://github.com/. If those pass, the sub-agent MAY proceed using gh — fetch remote content withgh api repos/{owner}/{repo}/contents/{path}, edit locally, push viagit pushto a working branch, thengh pr create. The PR body, staleness check, and writeback contract are UNCHANGED — only the transport differs. - Neither available (MCP missing AND gh CLI blocked in child sandbox even though parent has it): DO NOT silently fail. Refuse the sync, report
submitted: falsewith reasontransport-unavailable-in-child-sandbox, and ask the parent to either (a) re-run inline (parent context has the missing tools), or (b) install GitHub MCP viaclaude mcp add githuband retry.
This asymmetry is a real failure mode. Detecting it explicitly avoids burning the circuit-breaker failures counter on entries that would succeed if the parent ran them inline.
Decision tree — which reference do I load?
Section titled “Decision tree — which reference do I load?”Load only the reference you need (each is self-contained):
| Intent | Load |
|---|---|
| Check if CWD is a consumer project (first step, always) | references/consumer-guard.md |
| Sub-agent invocation template + auto-lesson writeback contract | references/sub-agent-invocation.md |
| Pre-flight checks (MCP, repo access, staleness, missing-target structural-stall guard) | references/preflight-checks.md |
Cross-ref health pre-check — don’t inherit red CI from a pre-existing-broken main | references/crossref-health-precheck.md |
| Mandatory pre-triage review BEFORE opening any PR | references/pre-triage-review.md |
Ctx-budget headroom check — diff touches any .claude/rules/*.md | references/ctx-budget-headroom.md |
| Resolve file origin + compute target path in kit repo | references/routing-and-paths.md |
| Open the PR (branch → push → create, fork flow, PR body template) | references/open-pr.md |
Writeback submitted: true to the lesson queue | references/queue-writeback.md |
| Error table, cross-platform notes, what gets synced / excluded | references/error-handling.md |
Gate tier — scale the ceremony to the diff
Section titled “Gate tier — scale the ceremony to the diff”Resolve the tier ONCE, from the captured payload, before loading any conditional reference. A one-line diff must not pay the ceremony of a 20-file multi-kit sync.
| Diff shape | Tier |
|---|---|
One kit, ≤3 files, no .claude/rules/*.md, no skill/agent create or rename, no module.json | fast |
Anything else — multi-kit, >3 files, touches rules/*.md, creates/renames a skill or agent, touches module.json | full |
Fast tier skips: references/ctx-budget-headroom.md (n-a — no rules/*.md in the diff), t1k-skill-creator validate when the diff touches no SKILL.md / references/*.md, and the naming-prefix + modules-registry gates (n-a by construction). Steps 3–4 of references/pre-triage-review.md collapse to one line each: generic: <1 sentence> and adversarial: n/a — trivial diff.
Fast tier never skips: Pre-flight Step 0 in full, and the secret / absolute-path / empty-file scans. In particular the main cross-ref pre-check is NOT tiered — it validates the target kit’s main tree, not your diff, so a one-line PR inherits a red main exactly like a large one; it is one sub-second script run, not agent reasoning.
Record the resolved tier in the PR body: **Gate tier:** {fast | full}.
Pre-Triage Review — MANDATORY
Section titled “Pre-Triage Review — MANDATORY”Every sync-back PR MUST contain a ## Pre-Triage Review block in the body — risk classification, gate pre-check results, generic-rationale verification, adversarial self-review, recommended triage disposition. This shifts review cost from triage (where it’s repeated per run) to filing (where it happens once). Triage’s t1k-code-reviewer just verifies the block.
Procedure + body template + failure modes (refuse-to-PR cases): references/pre-triage-review.md. The block sits AFTER ## Changed files in references/open-pr.md’s PR body template.
Pre-flight Step 0 — Kit-Freshness Guard (MANDATORY)
Section titled “Pre-flight Step 0 — Kit-Freshness Guard (MANDATORY)”Before ANY other sync-back step (scan, routing, pre-triage, branch creation), the sub-agent MUST run these IN ORDER — the payload is captured before anything can overwrite it:
-
Capture the payload FIRST. Read every file in the sync-back diff and hold its content (copy to a temp dir) BEFORE running any update.
t1k modules updateextracts the release ZIP without a per-file content diff, so a local file that has diverged ahead of the kit is silently reverted by it — including the very edit you are here to sync. Seerules/prefer-local-over-global-edits.md§ “Gotcha — Updates CAN clobber divergent local edits”. -
Skip the refresh when the payload is ahead of the kit. If a captured file differs from the kit-shipped content it was installed from, do NOT update: sync-back FIRST, let the kit re-release, then update (
rules/prefer-local-over-global-edits.md§ “Gotcha — Updates CAN clobber divergent local edits”, #367 EXCEPTION). Recordkit-freshness: skipped (local ahead)in the PR body and jump to step 4. -
Otherwise refresh local kits — NON-INTERACTIVELY. Run
t1k modules update --yesto pull every installed kit to its latest published tag — on the nothing-pending path this itself printsAll modules are up to date.(that string ist1k modules update’s own confirmation, nott1k --version’s). Then runt1k --versionas a SECOND confirmation: it prints one badge PER installed kit (<kit>@<tag> ... ✓ up to dateor⚠ update available), never a single global “all clear” line, and local-scope and global-scope installs report separately and can disagree — confirm every kit badge reads✓ up to daterather than looking for a matching string. The--yes(-y) flag is required, not optional: without it the command ends at anApply N update(s)?confirm prompt that a background sub-agent can never answer — with stdin closed it exits in ~1s having applied nothing (a silent no-op indistinguishable from “already current”), and with stdin held open it stalls until the Bash tool timeout. -
Check GitHub HEAD for unreleased commits — PER FILE. For EACH file in this sync-back diff, run a per-file commit query against the target kit repo:
Terminal window gh api 'repos/The1Studio/<target-kit>/commits?path=<file>&per_page=5' \--jq '.[] | {sha: .sha[0:8], date: .commit.author.date, msg: (.commit.message | split("\n")[0])}'The
?path=filter is mandatory — the global top-5 view drops per-file commits when the kit is active and unrelated commits push them off the top, producing a silent false-clear. The?path=value MUST be the kit repo’s real nested path (.claude/modules/<module>/skills/<name>/...), never the consumer-flattened one (.claude/skills/<name>/...) —?path=returns[]for BOTH “genuinely no commits” and “wrong path”, so a flattened-path guess produces a silent false-clear, and for an “already exists upstream?” check reads as “no upstream home” (duplicate-skill risk). Ground-truth the real path first withgit -C <target-kit-clone> ls-tree -r origin/main --name-only | grep -i '<name>', THEN query?path=with it; if no clone is handy, clone shallow (--depth 1) —git ls-remotecannot substitute (it lists refs, not paths). If the top result for any path is newer than the SHA your local file is based on, READ that commit’s diff viagh api repos/.../commits/<sha>and either rebase local edits onto kit HEAD (clone the kit, cherry-pick) BEFORE opening the PR, or abort the sync-back. Never silently push a stale-base branch.Multi-file diff loop example:
Terminal window for f in $(git diff --name-only main); doecho "=== $f ==="gh api "repos/The1Studio/<target-kit>/commits?path=$f&per_page=5" \--jq '.[0] | {sha: .sha[0:8], date: .commit.author.date, msg: (.commit.message | split("\n")[0])}'done⚠️ Gotcha — the naive 3-way merge-base silently drops corrections that predate your reconciliation commit. When step 4 finds upstream has moved and step 4 tells you to “rebase local edits onto kit HEAD,” do NOT default to
git merge-file <upstream-current> <base> <local-current>withbase = (your local fix commit)^. If the local content was previously clobbered by a badt1k modules updateand then restored (rules/prefer-local-over-global-edits.md§ “Gotcha”), the restoring commit’s parent is the CLOBBERED state — but any correction that predates the clobber (i.e. was already correct in a still-earlier commit, survived the clobber invisibly because the restore just brought it back) is already present in that parent, so the 3-way diff betweenbaseandlocal-currentsees no change there and silently drops it when merged onto upstream. No conflict, no error — the content is just gone.Detection step (run before every sync-back commit, not optional): for each file, diff the merge OUTPUT against
local-current(theirs) directly. The expected shape is “differs fromtheirsby nothing except theversion:frontmatter line” (upstream’s version number is usually the only thing legitimately newer). If a file’s merge output instead comes back with zero diff frommine(upstream-current) — i.e. the merge changed nothing at all — that is the tell: the local correction predates your chosen base and was eaten. Fix by rebuilding that file’s result aslocal-currentverbatim with only theversion:line swapped to upstream’s, rather than trusting the 3-way diff.Real instance:
theonekit-unity#499(2026-08-23, DOTS-AI). The first merge pass used(reconciliation commit)^as base for 15 files; it silently discarded the fix in 9 of them, including both worked<example>blocks ont1k-unity-dots-core-compile-doctor.md’sdescription:field (present since an earlier commit, invisible to the chosen base→local diff) and theBossWindUp→BossWindUpSystem/WindUpVisualCueRequestrename int1k-unity-dots-combat-boss/SKILL.md. Caught only by running the per-filediffagainsttheirsdescribed above — every eaten file showed 0 diff frommine. -
Cross-ref health check against
main(MANDATORY). Before pushing the branch, runcheck-skill-cross-refs.cjsagainst the target kit’s currentmaintree (in the kit clone you opened for the PR). Ifmainalready has broken skill cross-refs, every new sync-back PR inherits that red gate. Pre-existing breakage → either bundle the fix in this PR or refuse + surface (submitted: false, error: "main-crossref-broken: <refs>"). NEVER open a PR you know will land red on a pre-existing-brokenmain. Full procedure + decision table + PR-body record:references/crossref-health-precheck.md. -
Only after steps 1–5 pass clean, proceed to the normal sync-back flow (scan, routing, pre-triage, etc.).
Why this gate exists
Section titled “Why this gate exists”- Sync-back is a one-way push. A stale local will “regress” commits the sub-agent never saw — silently reverting other contributors’ work.
- The refresh itself is destructive if run first.
t1k modules updateextracts the release ZIP without a per-file content diff, so an edit that is ahead of the kit is reverted. Steps 1–2 (capture first, skip the refresh when local is ahead) are what make the rest of this gate safe to run. - GitHub HEAD ≠ published HEAD. Modules ship as bundled
modules-YYYYMMDD-HHMMtags on a schedule; in between, the kit repo accumulates unreleased commits.t1k modules updatebrings you to the latest TAG, not the latest commit onmain. The step 4 HEAD check closes this gap.
Originating incidents for this gate: references/preflight-checks.md § “Why Step 0 exists”.
Narrow exceptions
Section titled “Narrow exceptions”- Cwd is INSIDE a kit source repo (
theonekit-*) — sync-back is irrelevant; consumer-guard refuses earlier. Skip this step. - Same session already ran
t1k modules update< 30 minutes ago AND has no reason to suspect a new release shipped since. - GitHub is unreachable (network failure) — step 4 may be skipped, but the sub-agent MUST tell the user “skipped GitHub HEAD check; sync-back may conflict with unreleased kit commits” and have them confirm before opening the PR.
Step 0.6 — Issue Claim Check (when sync-back is for an issue-linked change)
Section titled “Step 0.6 — Issue Claim Check (when sync-back is for an issue-linked change)”Before opening any sync-back PR for a change that is linked to a GitHub issue (owner/repo#N), run:
node .claude/scripts/t1k-issue-claim.cjs check <owner/repo#N>- If
state == "held"by another user: do NOT open a duplicate sync-back PR. Surface theholderandprNumber, report them to the caller, and stop. - If
state == "free"or"stale"or"skip": proceed to open the PR.
When the sync-back PR IS opened: the PR itself becomes the claim. Use the acquire subcommand to self-assign the issue and get the body marker:
node .claude/scripts/t1k-issue-claim.cjs acquire <owner/repo#N> --pr <new-PR-number>Include in the PR body:
Fixes #N(thebodyTrailerreturned byacquire)- The
markerLinereturned byacquire(e.g.,t1k-claim: owner/repo#N) - Apply the label returned as
labelToApply(e.g.,t1k:claim)
The sync-back PR with these three elements IS the durable claim — no separate claim step needed. Do NOT run gh issue edit or gh pr create for claiming; the script and PR body carry the claim.
This step is additive: the Kit-Freshness Guard (Step 0 above) MUST still run first.
Operational notes
Section titled “Operational notes”- Scan BOTH user-scope and project-scope. Walk
$HOME/.claude/AND<cwd>/.claude/when collecting candidate files. Project-scope modules/skills (e.g., a wiki repo shipping its own module) are invisible if you only scan user-scope. Seereferences/routing-and-paths.mdStep 0 for the rules. Related: #168. - Pre-flight: requires t1k CLI v4.17.0+. The
require-current-cli.cjsPreToolUse hook will block stale-CLI invocations oft1k sync-back(and other state-mutating commands) when the cachedlatestversion is newer than the local binary. Runt1k self-updatefirst if the gate fires; override (NOT recommended) isT1K_REQUIRE_CURRENT_CLI=0. - Run pre-flight checks BEFORE any file write — verify GitHub MCP is connected.
- Staleness check is MANDATORY. Never push a stale branch silently.
- Worktree, never
checkout, in the kit clone. Steps 3 and 5 above both send you into a kit clone (to rebase a stale base, and to runcheck-skill-cross-refs.cjsagainstmain). That clone is shared with other live Claude sessions:git checkout/switchmoves the one sharedHEADand derails whichever session is mid-commit. Acquire the branch as a worktree instead —git -C <clone> fetch -q origin && git -C <clone> worktree add -b <branch> <path> origin/main— inspect read-only withgit -C <clone> show <ref>:<path>, andgit -C <clone> worktree remove <path>once the PR is open. If the branch is already checked out in another worktree, take a detached one (worktree add --detach <path> origin/<branch>) and push withgit push origin HEAD:<branch>. Full procedure: thet1k-kit-developeragent’s § “Shared-Clone Discipline”. - Module-registry-sync: if the edit touches
module.json, the sub-agent must also regeneratet1k-modules.jsonin the kit repo (gate #validate-modules-registry-sync). - Skill-rename + activation-fragment sync — when a sync-back diff includes a skill directory rename (e.g.,
t1k-rn-rn-base-old→t1k-rn-rn-base-new), the sub-agent must ALSO update everyt1k-activation-*.jsonfragment whosesessionBaseline[]ormappings[].skills[]references the old slug. Bare-slug refs (old) and full-prefixed refs (t1k-rn-rn-base-old) both need to swap. The release-action prefixer’sbuildSelfHealMap()will catch the rename on the next CI run, but shipping the activation update in the same PR keeps the SSOT consistent and avoids the per-PR drift safety-net gate (validate-activation-skill-resolution.cjs) firing on subsequent unrelated PRs against the kit. If unsure which fragments reference the renamed skill, grep:grep -rE '"(old-slug|t1k-...-old-slug)"' $HOME/.claude/. - Rules ctx-budget headroom — if the diff CREATES or MODIFIES any
.claude/rules/*.mdin the receiver kit, the sub-agent MUST runnode .claude/scripts/sync-back-ctx-budget.cjs --rules-dir <receiver-rules-dir> --change <relpath>=<contentfile> ...BEFORE pushing the branch. Statusoverflowmeans the projected corpus would breach a hard cap enforced byvalidate-context-window-budget.cjs— 16 500 tokens kit-wide, or 6 000 for a rule landing under.claude/modules/<name>/rules/(pass--scope module:<name>, #691) — invokeAskUserQuestionto choose restructure / abort / override. Full algorithm + decision options:references/ctx-budget-headroom.md. Motivating incident: PRs #264, #269, #274 each broke the cap on first push. - Marker-namespace sync — if the changeset adds any
[t1k:<name>emission in a.cjs/.js/.tsfile, the sub-agent MUST runnode $HOME/.claude/scripts/sync-marker-namespaces.cjs --root <kit-root>in the local kit clone before pushing the branch. The script auto-appends stub rows for new prefixes todocs/marker-namespaces.md. After it runs, fill in the Purpose/Emitter/Consumer columns in each stub row and include thedocs/marker-namespaces.mdchange in the same commit. This prevents thevalidate-marker-namespacesCI gate from blocking the PR. Skip this step only when the kit has nodocs/marker-namespaces.md(non-core kits that use core’s registry). - PR is created; NOT automerged. End every invocation by reporting the PR URL and noting “review + merge in the kit repo.”
- Security: never sync
.env,settings.local.json, memory files, or files with secrets. Sanitize absolute paths before writing. - Skill-touching syncs MUST consult
t1k-skill-creator. If any path in the diff matches$HOME/.claude/skills/*/SKILL.mdor$HOME/.claude/skills/*/references/*.md, the sub-agent invokest1k-skill-creatorto validate Skillmark structure (frontmatter shape, line-count cap from gate 2, description cap from gate 3, decision-tree pattern) BEFORE opening the PR. Validation failures block the PR; warnings surface in the PR body so the maintainer reviewer sees them. - Naming-prefix gate on creates AND renames. If the diff CREATES or RENAMES a skill directory or agent file, the sub-agent MUST verify the new name conforms to the universal
t1k-prefix rule (skills/t1k-skill-creator/references/architecture-rules.md§ 0 /skills/t1k-agent-creator/references/architecture-rules.md§ 0): core =t1k-{slug}, kit-wide =t1k-{kit}-{slug}, module-scoped =t1k-{kit}-{module}-{slug}. Frontmattername:MUST match the directory/file basename. Non-conforming names will failvalidate-skill-prefix.cjs/validate-agent-prefix.cjsat PR time — catch them here and reject the sync-back with a clear error message instead of opening a PR that will block on CI. Agent rename gap:auto-prefix-agents.cjsdoes NOT rewrite per-modulemodule.jsonagents/routingOverlayfields, so the sync-back sub-agent must hand-update both manifests when renaming agents. - CI-prefix lookup BEFORE computing target path (consumer→kit name asymmetry). Skill names in the consumer-side install are CI-prefixed at release time by
auto-prefix-skills.cjs— e.g., a kit-source path of$HOME/.claude/modules/cocos-base/skills/playable-async-utilities/SKILL.mdships to consumers ast1k-cocos-cocos-base-playable-async-utilities. When sync-back resolves a target path in a kit source repo, it MUST NOT reuse the prefixed consumer-side directory name verbatim. Instead, look up the unprefixed source slug via either (a) the skill’s frontmatterorigin+modulefields plus the kit’s.t1k-manifest.json(kit-flat) /modules/{module}/.t1k-manifest.json(modular), or (b) the GitHub Tree API:gh api repos/{owner}/{repo}/git/trees/main?recursive=1 --jq '.tree[].path | select(endswith("/SKILL.md"))'then match by basename or by reading frontmattername:.references/routing-and-paths.mdStep 2 examples currently show only the kit-flat unprefixed case — assuming the consumer name maps 1:1 to the kit path will mis-target every modular kit (theonekit-cocos, theonekit-unity, theonekit-designer). Always probe before writing.
Contribution Scoring
Section titled “Contribution Scoring”Do NOT score the PR at create time. type=sync-back-pr earns merged-tier credit, so the worker requires the PR to be MERGED and returns 403 {"error":"author_attribution_failed","reason":"pr_not_merged"} for a just-opened one. A sync-back PR is open by definition at the moment gh pr create returns, so an immediate POST can only ever be rejected.
Credit is recorded on the merge instead, with no action needed here: the contribution-capture.cjs PostToolUse hook records the gh pr create ref deterministically, and t1k:contribution-flush retries it each session until the PR merges and the POST is accepted. Do NOT hand-append the tracking JSONL — the hook owns capture.
See $HOME/.claude/skills/t1k-contribution-score/SKILL.md for the full invocation contract (rubric, endpoint resolution, POST contract). Do NOT inline rubric or POST logic here — the SSOT lives in that skill.