page:docs-specs-platform-gaps-phase0-intake-rehearsal
WP-RN-02 record: public-push guard and intake rehearsal reference
Executed 2026-09-28 on branch phase0/hard-split-registry-guards of a5c-ai/fabric. Spec: docs/specs/platform-gaps/15-private-isolation-rename.md WP-RN-02 (objective: codify the §1.4 intake process; make pushing fabric content to the public repository mechanically impossible from CI). Policy: docs/DIVERGENCE-POLICY.md; procedure: docs/UPSTREAM-INTAKE.md; cutover baseline: watch-baseline.md. The public repository a5c-ai/babysitter was read from and never written to (pushedat stayed 2026-09-16T19:59:50Z throughout; only git fetch upstream --no-tags, git ls-remote, gh api GET and gh pr list -R a5c-ai/babysitter were issued against it).
WP-RN-02 record: public-push guard and intake rehearsal
Executed 2026-09-28 on branch phase0/hard-split-registry-guards of a5c-ai/fabric. Spec: docs/specs/platform-gaps/15-private-isolation-rename.md WP-RN-02 (objective: codify the §1.4 intake process; make pushing fabric content to the public repository mechanically impossible from CI). Policy: docs/DIVERGENCE-POLICY.md; procedure: docs/UPSTREAM-INTAKE.md; cutover baseline: watch-baseline.md. The public repository a5c-ai/babysitter was read from and never written to (pushed_at stayed 2026-09-16T19:59:50Z throughout; only git fetch upstream --no-tags, git ls-remote, gh api GET and gh pr list -R a5c-ai/babysitter were issued against it).
What landed
| Item | Location |
|---|---|
| Guard script | scripts/ci/public-push-guard.cjs (CommonJS, Node >= 22, no dependencies; --self-test, --root, --json) |
| Fixtures | scripts/ci/fixtures/public-push-guard/negative/ (13, each declares expect: <rule>), .../positive/ (9) |
| Unit tests | scripts/ci/public-push-guard.test.mjs (node:test), npm run test:public-push-guard; scan alone: npm run guard:public-push |
| CI job | public-push-guard in .github/workflows/ci.yml (permissions contents: read; --self-test, then asserts the checkout has no pushable remote for the public repository) |
| Intake procedure | docs/UPSTREAM-INTAKE.md |
| PR label | upstream-intake on a5c-ai/fabric (0E8A16, "Cherry-picked from public a5c-ai/babysitter") |
| PR template | .github/PULL_REQUEST_TEMPLATE/upstream-intake.md (the default PULL_REQUEST_TEMPLATE.md is untouched) |
| Policy link | docs/DIVERGENCE-POLICY.md §4 now points at the real intake doc, template, guard and this record |
Guard rules
| Rule | Trips on | Allowed form |
|---|---|---|
push-to-public | git push <public url>; git push <remote> when the file binds that remote to the public repository | git push origin ... |
pushable-remote | git remote add / git config remote.<n>.url / git clone / gh repo clone of the public repository without git remote set-url --push <n> no_push (or remote.<n>.pushurl no_push) in the same file | the fetch-only idiom of .github/workflows/upstream-propagation-watch.yml |
push-url-set | git remote set-url --push <n> <public url>, git config remote.<n>.pushurl <public url> | ... no_push |
ssh-url | git@github.com:a5c-ai/babysitter outside a git fetch / ls-remote / remote-binding line | git fetch git@github.com:a5c-ai/babysitter.git main |
gh-write | gh api with -X POST/PUT/PATCH/DELETE or request fields against repos/a5c-ai/babysitter...; any non-read gh <group> <verb> -R a5c-ai/babysitter (pr create, release create, workflow run, ...) | gh api repos/a5c-ai/babysitter --jq ..., gh pr list -R a5c-ai/babysitter |
checkout-with-write | actions/checkout with repository: a5c-ai/babysitter plus token:, ssh-key:, persist-credentials: true, or contents: write / write-all in the file | persist-credentials: false under contents: read |
action-targets-public | any other step whose with: block (including JSON inside prompts) carries repository: / repo: = the public repository | uses: a5c-ai/babysitter/<path>@<ref> itself; ${{ github.repository }} |
submodule-public | .gitmodules url pointing at the public repository | — |
Scanned surface: .github/**, scripts/** (minus the guard, its test and its fixtures), package.json, .npmrc, .gitmodules; file types .yml .yaml .json .cjs .mjs .sh. Comments and Markdown are never findings; @a5c-ai/* npm names never match. Escape hatch: # public-push-guard: allow <reason> (trailing or on the preceding comment line; the reason is mandatory, a bare marker still fails).
Evidence: guard
$ node scripts/ci/public-push-guard.cjs --self-test
PASS fixtures present -- 13 negative, 9 positive
PASS negative scripts/ci/fixtures/public-push-guard/negative/action-targets-public.workflow.yml trips -- rules: action-targets-public
PASS negative .../allow-marker-without-reason.workflow.yml trips -- rules: push-to-public
PASS negative .../checkout-with-token.workflow.yml trips -- rules: checkout-with-write
PASS negative .../checkout-write-permissions.workflow.yml trips -- rules: checkout-with-write
PASS negative .../clone-without-no-push.sh trips -- rules: pushable-remote
PASS negative .../gh-api-post.sh trips -- rules: gh-write
PASS negative .../gh-pr-create.workflow.yml trips -- rules: gh-write
PASS negative .../git-push-https.workflow.yml trips -- rules: push-to-public
PASS negative .../package-scripts.json trips -- rules: push-to-public
PASS negative .../push-named-remote.sh trips -- rules: push-to-public
PASS negative .../remote-add-ssh-without-no-push.workflow.yml trips -- rules: pushable-remote
PASS negative .../set-url-push.sh trips -- rules: push-url-set
PASS negative .../submodule/.gitmodules trips -- rules: submodule-public
PASS positive .../allow-marker.sh passes -- 2 suppressed by allow marker
PASS positive .../checkout-read-only.workflow.yml passes
PASS positive .../config-push-url-no-push.sh passes
PASS positive .../documentation.md passes
PASS positive .../fetch-only-upstream.workflow.yml passes
PASS positive .../gh-read-only.sh passes
PASS positive .../npm-names.workflow.yml passes
PASS positive .../package-metadata.json passes
PASS positive .../uses-action-reference.workflow.yml passes
PASS repository scan (118 files) passes
public-push-guard self-test: 24/24 assertions passed
public-push-guard: self-test passed; repository scan clean (118 files, 0 findings)
$ npm run test:public-push-guard
# tests 14 # pass 14 # fail 0The first real scan (before any fix) reported three findings, all in inherited, disabled agent workflows, and they were fixed rather than allow-listed:
.github/workflows/issue-triage-dispatch.yml:76: [action-targets-public] step "uses: a5c-ai/babysitter/packages/adapters/triggers@staging" is pointed at a5c-ai/babysitter
> { "issueNumber": ${{ inputs.issue_number || github.event.issue.number }}, "repo": "a5c-ai/babysitter" }
.github/workflows/live-stack-gardening.yml:51: [pushable-remote] git clone binds remote "origin" to a5c-ai/babysitter without `git remote set-url --push origin no_push`
> git clone https://github.com/a5c-ai/babysitter.wiki.git /tmp/babysitter-wiki
.github/workflows/live-stack-gardening.yml:73: [action-targets-public] step "uses: a5c-ai/babysitter/packages/adapters/triggers@staging" is pointed at a5c-ai/babysitter
> Inputs: { "repo": "a5c-ai/babysitter", "wikiPath": "/tmp/babysitter-wiki" }Both workflows now act on ${{ github.repository }} (an agent dispatched from fabric with a token must not triage the public repository's issues or edit its wiki). js-yaml loads ci.yml, live-stack-gardening.yml and issue-triage-dispatch.yml; the new job's steps are Checkout repository, Setup Node.js, Guard self-test and repository scan, No pushable remote for the public repository, permissions {"contents":"read"}.
Rehearsal: candidate review
git fetch upstream --no-tags then git log --oneline --remotes=upstream --not --remotes=origin listed 469 public commits absent from every fabric ref; origin/main..upstream/main and origin/staging..upstream/develop (filtered) were empty; origin/staging..upstream/staging held three. Nearly all 469 are bot commits on agent-versions/daily-*, model-versions/daily-* and changelog/daily-* branches. Evaluation per docs/UPSTREAM-INTAKE.md §2:
| Candidate | Branch | Decision | Reason |
|---|---|---|---|
d98b7ff70 docs: deployment is handled from a private deployment repository | staging | rejected | every hunk says deployment happens outside the repository; fabric is the deployment repository (and git apply --check already failed: fabric's docs/development/06-kradle-cloud-platform.md has moved on) |
02e3a8773 ci: remove the cluster deploy pipeline from the public publish workflows | staging | rejected | deletes publish.yml deploy jobs and g0-rt-jitsi-e2e.yml, which fabric runs |
64fd9f475 test(ci): pin publish workflows to have no cluster deploy jobs | staging | rejected | asserts the absence of jobs fabric has |
d5c7e7f77 fix(sdk): avoid non-portable fallback sdk symlinks | agent/issue-1757 | rejected | 3-way cherry-pick (tried in a throwaway worktree, then aborted) conflicted in packages/babysitter-sdk/src/cli/main/runSupport.ts:291-303 and packages/genty/platform/.../validation.ts:107-116, exactly where public main later chose relative symlink targets (ded587848, 2026-08-14, already in fabric); superseded upstream, fabric wins leaves nothing |
442e7ed6a feat(sdk): expose runId, runDir, artifactsDir on ProcessContext | feat/expose-runid-artifactsdir-on-process-context | rejected | already in fabric (packages/babysitter-sdk/src/runtime/types.ts carries the identical fields and doc comments); the commit predates the public packages/sdk -> packages/babysitter-sdk move (47fd0e280, 2026-06-06) |
58891e42a docs: apply enforcement messaging | docs/improve-readme-and-best-practices | rejected | reverted on its own branch by b62f317ac |
776123a8d fix: create CONTRIBUTING.md | copilot/fix-broken-contributing-link | rejected | already reachable from fabric refs (0 commits beyond origin) |
44e7f47ba docs: daily changelog update (2026-08-05) | changelog/daily-2026-08-05 | rejected | modifies CHANGELOG.md where fabric has diverged (fabric-only kip-sdk entries) and a docs/daily-updates/2026-08-05.md created earlier on the same bot branch |
12eafcc00 / 96ad4691b Cursor and Hermes records / evidence sources for 2026-07-15 | agent-versions/daily-2026-07-15 | deferred | natural tight-series follow-up to the commit taken below; left out so the rehearsal is exactly one commit |
76434c235 Add upstream agent versions for 2026-07-15 | agent-versions/daily-2026-07-15 | **taken** | one commit, one new file, same shape as the 2026-07-14 and 2026-07-17 records fabric already has; fills a gap in fabric's own catalog data; nothing about it misdescribes fabric |
Rehearsal: the documented flow, as run
$ git remote -v
origin git@github.com:a5c-ai/fabric.git (fetch)
origin git@github.com:a5c-ai/fabric.git (push)
upstream https://github.com/a5c-ai/babysitter.git (fetch)
upstream no_push (push)
$ git fetch upstream --no-tags
$ git switch -c intake/76434c2 # off phase0/hard-split-registry-guards at 030920ec6
$ git cherry-pick -x 76434c235
1 file changed, 286 insertions(+)
create mode 100644 packages/atlas/graph/agent-stack/agent-versions/upstream-current-2026-07-15.yaml
$ git log -1 --format='%h %an %s%n%b'
d39bba75d a5c-ai[bot] Add upstream agent versions for 2026-07-15
(cherry picked from commit 76434c235de18ea1c0aceb6f5297a87cef9e861c)
$ node -e '<js-yaml structural check>'
07-15 docs: 8 kinds: AgentVersion
07-14 docs: 10 kinds: AgentVersion
same top-level keys as 07-14: true
files: 35 ids: 177 unparsable (pre-existing): pi-issue-541.yaml
duplicate ids involving the new file: none
new ids: agentVersion:amp:0-0-1784091630-gf43d5c, agentVersion:claude:2-1-210, agentVersion:claude-agent-sdk:0-3-210,
agentVersion:codex-sdk:6-47-0, agentVersion:droid:0-172-0, agentVersion:omp:16-5-2, agentVersion:opencode:1-18-1, agentVersion:pi:0-80-7
$ npm run build:index --workspace=@a5c-ai/atlas # the consumer of these records
[atlas:indexer] wrote dist/index.json
[atlas:indexer] stats {"totalRecords":17715,"totalEdges":88686,"totalNodeKinds":245,"totalEdgeKinds":408,"totalClusters":20,"yamlFiles":2745,"parseErrors":1}
(exit 0; the one parse error is pi-issue-541.yaml, which fails strict YAML at 030920ec6 too; all 8 new ids present in dist/index.json)
$ node scripts/ci/public-push-guard.cjs --self-test # 24/24, repository scan clean
$ git push -u origin intake/76434c2
$ gh pr list -R a5c-ai/babysitter --head agent-versions/daily-2026-07-15 --state all # read-only provenance lookup
1422 OPEN https://github.com/a5c-ai/babysitter/pull/1422 Add upstream agent versions for 2026-07-15
$ gh pr create -R a5c-ai/fabric --base phase0/hard-split-registry-guards --head intake/76434c2 --label upstream-intake \
--title "intake: Add upstream agent versions for 2026-07-15 (cherry-pick 76434c235)" --body-file <template filled in>
https://github.com/a5c-ai/fabric/pull/2
$ gh pr view 2 --json state,labels,baseRefName,headRefName
{"base":"phase0/hard-split-registry-guards","head":"intake/76434c2","labels":["upstream-intake"],"number":2,"state":"OPEN"}
$ git switch phase0/hard-split-registry-guardsPath map recorded in the PR: packages/atlas/graph/agent-stack/agent-versions/upstream-current-2026-07-15.yaml -> identical path (new file). Conflicts: none. Dropped hunks: none.
Status against the acceptance criteria
- AC1 (intake doc: remote setup, labeling, path map, conflict policy):
docs/UPSTREAM-INTAKE.md§1, §4, §5, §6. Done. - AC2 (CI guard fails push targets and checkout-with-write): guard +
public-push-guardjob; negative fixtures prove both classes trip. Done. - AC3 (rehearsal cherry-pick of one real upstream commit lands via the documented flow with its path map recorded): pull request **opened** — <https://github.com/a5c-ai/fabric/pull/2> — through the documented flow, labeled, path map recorded. **Not merged** by WP-RN-02; merging is the owner's decision (the branch ruleset requires a reviewed PR anyway). The spec's test requirement "rehearsal PR merged and referenced" therefore stays open until the owner merges #2; the reference is recorded here and in
docs/UPSTREAM-INTAKE.md§9. - Test requirement (self-test fixture asserting the guard trips):
--self-testandscripts/ci/public-push-guard.test.mjs. Done. - WP-RN-01 deferred requirement ("CI check from WP-RN-02 green"): the
public-push-guardjob runs on every pull request. First run, on the CI run of #2 (pull_request, run 36395354707): jobPublic-push guard (two-repo split)<https://github.com/a5c-ai/fabric/actions/runs/36395354707/job/108840321702> concludedsuccess; stepsCheckout repository,Setup Node.js,Guard self-test and repository scan,No pushable remote for the public repositoryallsuccess. Green.
Rollback
| Piece | Rollback |
|---|---|
| Guard, job, fixtures, tests, docs, template, workflow input fixes | git revert 030920ec6 (and the follow-up docs commit) |
| Label | gh label delete upstream-intake -R a5c-ai/fabric (do not: the propagation watch keys on it) |
| Rehearsal PR | close #2 without merging; git push origin --delete intake/76434c2 |
Nothing here touched the public repository, so there is nothing to roll back there.