Skip to content

fix(release): harden npm-publish pipeline + document release SOP - #744

Merged
rafaelscosta merged 2 commits into
mainfrom
fix/release-pipeline-hardening
May 17, 2026
Merged

fix(release): harden npm-publish pipeline + document release SOP#744
rafaelscosta merged 2 commits into
mainfrom
fix/release-pipeline-hardening

Conversation

@rafaelscosta

@rafaelscosta rafaelscosta commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Captures three real failure modes observed during v5.2.5 and v5.2.6 releases and prevents them from biting future releases. Companion to PR #742 (the original Pro install hotfix) and PR #743 (the 5.2.6 release bump).

What changed

npm-publish.yml

  1. Race condition fix: publish_legacy_aiox_core now needs: [test, build, publish] instead of [test, build]. Was running in parallel with publish even though the compat wrapper transitively depends on the scoped package, causing the smoke npx step to race against npm CDN propagation.

  2. Smoke test resilience: legacy npx smoke retry budget 6×15s → 16×15s (90s → 240s). Now checks BOTH aiox-core and @aiox-squads/core visibility before invoking npx. Captures last npx stderr in the timeout error.

  3. Notify clarity: replaced the aggregated bool with a structured summary that distinguishes hard fail (scoped publish) from soft warnings (workspace, legacy compat, smoke tests). Hard fail blocks; soft warnings just inform.

installer-smoke-matrix.yml

  1. Windows path escape: ${{ github.workspace }} interpolated into node -e "..." on Git Bash for Windows turned D:\a\aiox-core\aiox-core into D:aaiox-coreaiox-core because \a becomes an escape sequence. Now passes via WORKSPACE_DIR env var. Applied to all 3 smoke install steps.

docs/guides/release-procedure.md (NEW)

Full SOP for @devops: pre-flight checks → version bump coordination → branch + PR → branch protection bypass dance (two systems, both enforce CODEOWNERS, must be relaxed/restored atomically) → tag + push → post-publish verification → known quirks → rollback. Cites incidents and PRs so the next operator knows WHY each step exists.

Validation

  • Both workflow YAMLs parse cleanly
  • Real E2E with @aiox-squads/core@5.2.6 from public npm + Pro 0.4.3 tgz in worst-case dir (workspace ancestor) → fix works, no escape, package installed at target
  • Smoke matrix Windows path fix validated when this PR's CI runs (the bug was reproducible in PR chore(release): bump to 5.2.6 — Pro installer workspace hijack hotfix #743's CI)
  • Legacy publish race fix validated on next release (will not affect this PR since this PR doesn't push a tag)

Test plan

This PR doesn't change runtime behavior of any package — only workflow files and documentation. Standard CI passes will indicate it's safe.

The real validation is in the NEXT release: when v5.2.7 or later ships, the legacy smoke test should complete cleanly within 240s and the Windows smoke matrix combos should pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Improved CI workflows for installer smoke testing and npm publishing to increase reliability, sequencing, and error reporting.
  • Documentation
    • Added a comprehensive release procedure SOP covering pre-flight checks, versioning, publishing gates, verification steps, rollback guidance, and common failure modes.

Note: Internal infrastructure and process improvements; no direct user-facing feature changes.

Review Change Stack

Three real failure modes observed during the v5.2.5/5.2.6 releases, each
fixed and documented so the next release does not re-discover them.

## npm-publish.yml

### Race: publish_legacy_aiox_core ran in parallel with publish

The legacy compat wrapper `aiox-core@X.Y.Z` declares
`@aiox-squads/core@X.Y.Z` as a runtime dependency. The smoke test step
runs `npx --yes aiox-core@X.Y.Z --version`, which transitively installs
the scoped package. With both jobs in parallel, `npx` raced against npm
CDN propagation of the scoped package and timed out after 90s — making
the entire job appear failed even though the publish step inside it
had already printed "✅ Published aiox-core@X.Y.Z".

Fix: `publish_legacy_aiox_core` now `needs: [test, build, publish]`,
serializing the legacy publish behind the scoped publish.

### Smoke test legacy npx: timeout too short, no diagnostics

Bumped retry budget 6×15s (90s) → 16×15s (240s). Added a second
visibility check for the scoped dependency before invoking `npx` so we
distinguish "compat not visible" from "scoped dep not visible". Captured
last `npx` stderr in the timeout message instead of failing silently.

### Notify: aggregated bool hid which step failed

Replaced with a structured summary that distinguishes hard fail (scoped
publish) from soft warnings (workspace, legacy, smoke). Only the scoped
publish is treated as release-blocking; the rest are informational and
can be republished out-of-band.

## installer-smoke-matrix.yml

### Windows path mangling in node -e via Git Bash

`${{ github.workspace }}` on a Windows runner expands to
`D:\a\aiox-core\aiox-core`. Interpolated into a `node -e "..."` script
running on Git Bash, the backslashes were interpreted as escape
sequences (`\a` → `a`), producing `D:aaiox-coreaiox-core` and
`Cannot find module ...`.

Fix: pass workspace path via `WORKSPACE_DIR` env var, read inside the
script as `process.env.WORKSPACE_DIR`. Applied to all 3 smoke install
steps.

## docs/guides/release-procedure.md

New SOP capturing the entire release pipeline:
- Pre-flight checks (lint, tests, tokens)
- 4-site version bump coordination (root, compat, installer, lock)
- Two-system branch protection bypass dance (modern ruleset +
  legacy branch protection both enforce CODEOWNERS independently;
  `gh pr merge --admin` bypasses neither in isolation)
- Atomic bypass→merge→restore as one shell block
- Tag-driven publish trigger
- Post-publish verification (registry sanity + artifact inspection)
- Known CI quirks with mitigations
- Rollback procedure (deprecate, don't unpublish)
- Failure modes seen in production with root causes

The SOP cites real PR numbers and incidents so future operators know
why each step exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aiox-core Ready Ready Preview, Comment May 17, 2026 11:01am

Request Review

@github-actions github-actions Bot added area: docs Documentation (docs/) area: devops CI/CD, GitHub Actions (.github/) labels May 17, 2026
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

This PR fixes Windows path escaping in three installer smoke-test Node scripts, tightens NPM publish orchestration and softens some failure semantics while extending package-propagation polling, and adds a comprehensive release-procedure SOP document.

Changes

Installer Smoke Test Windows Path Fix

Layer / File(s) Summary
Workspace directory environment variable in smoke install steps
.github/workflows/installer-smoke-matrix.yml
All three "Smoke install" steps now pass ${{ github.workspace }} via WORKSPACE_DIR env and update inline node -e scripts to use process.env.WORKSPACE_DIR for require paths to avoid Windows escape-sequence mangling.

NPM Publish Workflow Job Orchestration and Failure Handling

Layer / File(s) Summary
Legacy publish job dependency on main publish
.github/workflows/npm-publish.yml
publish_legacy_aiox_core job now depends on publish job completion and gates execution on publish.result being success or skipped.
Extended npm package propagation waiting
.github/workflows/npm-publish.yml
Legacy smoke test polls for both aiox-core@<version> and @aiox-squads/core@<version> with up to 16 attempts, retries npx, and reports last captured stderr on timeout.
Notify job structured summary and soft/hard failure classification
.github/workflows/npm-publish.yml
notify logic emits a formatted release summary, hard-fails only when the scoped publish job failed/cancelled, and treats workspace/legacy publish and smoke-test export failures as soft warnings.

Release Procedure SOP Documentation

Layer / File(s) Summary
Release SOP introduction and pre-flight checks
docs/guides/release-procedure.md
Introduces release authority and rationale; defines T-10 pre-flight gates covering git state, lint/tests, installer regression, CI status, and npm token verification.
Version bumping and branch/PR workflow
docs/guides/release-procedure.md
Describes synchronized T-5 version bump across files, lockfile regeneration, changelog rules, and branch/PR workflow with CI expectations.
Two-system branch protection bypass and restore
docs/guides/release-procedure.md
Describes atomic bypass→merge→restore using captured GitHub API state and jq payloads with restoration verification.
Tag/push and post-publish verification steps
docs/guides/release-procedure.md
Documents tag/push sequence triggering npm-publish.yml, registry/dist-tag checks, workspace cross-checks, artifact validation via npm pack, and optional E2E install verification.
CI quirks, rollback procedures, and historical failure modes
docs/guides/release-procedure.md
Lists CI quirks and mitigations, rollback via deprecate/repoint latest, and enumerates historical failure modes with targeted fixes (including Windows node -e escape workaround).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

area: workflows

Suggested reviewers

  • oalanicolas
  • Pedrovaleriolopez
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: hardening the npm-publish pipeline and documenting the release procedure, which directly align with the primary objectives of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-pipeline-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage Report

Coverage report not available

📈 Full coverage report available in Codecov


Generated by PR Automation (Story 6.1)

coderabbitai[bot]
coderabbitai Bot previously requested changes May 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/npm-publish.yml:
- Around line 581-594: The release summary unconditionally adds
"`@aiox-squads/core`@${VERSION}" to the PUBLISHED array and prints the success
block for both PUBLISH_RESULT="success" and "skipped", causing a false
"published" message when the publish job was skipped; update the script so the
scoped package is only added to PUBLISHED when PUBLISH_RESULT = "success" (use
PUBLISH_RESULT to gate adding "`@aiox-squads/core`@${VERSION}" to the PUBLISHED
array) and change the echo/print condition to run only when PUBLISH_RESULT =
"success" (remove "skipped" from the success check), ensuring PUBLISHED and the
success message accurately reflect actual publish results.

In `@docs/guides/release-procedure.md`:
- Around line 81-115: The current script can leave protections relaxed because
it reuses raw ruleset snapshots (which contain read-only fields that GitHub
rejects on PUT) and lacks a trap cleanup; fix by (1) creating sanitized restore
payloads using jq to remove read-only fields (e.g., delete(.id, .node_id,
.created_at, .updated_at, .owner, .repository) or similar) for the ruleset
restore file (/tmp/aiox-core-ruleset-*.json) and likewise extracting only
allowed fields for required_pull_request_reviews
(/tmp/aiox-core-prr-restore.json), (2) wrap the sequence in set -e and a trap
'restore' EXIT function that always PUTs the sanitized ruleset to
repos/SynkraAI/aiox-core/rulesets/13330052 and PATCHes
repos/SynkraAI/aiox-core/branches/main/protection/required_pull_request_reviews,
and (3) ensure the merge step (AIOX_ACTIVE_AGENT=devops gh pr merge <PR#>
--squash --admin --delete-branch) runs between the bypass DELETE/PUT and the
guaranteed restore in the trap so no failure leaves protection relaxed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b40f2d7-0d97-4496-b3a8-87b29ac3db94

📥 Commits

Reviewing files that changed from the base of the PR and between 9b837ed and b7ac9fe.

📒 Files selected for processing (3)
  • .github/workflows/installer-smoke-matrix.yml
  • .github/workflows/npm-publish.yml
  • docs/guides/release-procedure.md

Comment thread .github/workflows/npm-publish.yml Outdated
Comment thread docs/guides/release-procedure.md Outdated
Two real bugs caught:

## notify step printed "Successfully published" for skipped scoped publish

The previous condition gated the success block on
`PUBLISH_RESULT = success OR skipped` AND pre-seeded the PUBLISHED array
with `@aiox-squads/core@${VERSION}` unconditionally. Result: a workflow
run where `publish` was skipped (e.g. manual dispatch excluding `core`)
would falsely report the scoped package as published.

Fix: PUBLISHED is now built only from jobs that actually succeeded.
A separate branch handles the all-skipped case explicitly. The
success message only renders when something was actually published.

## SOP bypass dance: snapshot reuse could leave protections relaxed

The documented procedure piped the raw `gh api .../rulesets/...`
output back into `gh api -X PUT`. The raw response includes read-only
fields (`id`, `node_id`, `created_at`, `updated_at`, `_links`, `url`)
that GitHub rejects with HTTP 422 on PUT — meaning the restore could
silently fail and leave production in a relaxed state.

Two fixes layered for safety:

1. Snapshot is now separated from restore payload. Raw snapshot stays
   on disk only as a diff baseline. Sanitized restore payload is
   derived via `jq '{name, target, enforcement, conditions,
   bypass_actors, rules}'` — only writable fields.

2. The bypass→merge→restore sequence is wrapped in `set -e` + a
   `trap 'restore_protections' EXIT`. The trap fires on ANY exit
   (success, failure, signal) and idempotently re-applies the
   sanitized restore payloads. If a restore step itself fails, the
   trap emits `::error::` annotations so the failure surfaces in the
   GitHub Actions UI.

The previous documentation worked in practice because `gh api --input`
tolerates read-only fields in some paths, but that behavior is not
guaranteed. The new procedure is defensively correct under network
failures, manual interrupts, and edge-case API rejections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafaelscosta
rafaelscosta dismissed coderabbitai[bot]’s stale review May 17, 2026 11:01

Addressed in f422d85. (1) notify: PUBLISHED array only populated from jobs with result=success; success message only renders when something actually published; explicit branches for all-skipped vs no-publish-but-not-all-skipped. (2) SOP: snapshot now separated from restore payload (sanitized via jq projection of writable fields only); bypass→merge→restore wrapped in set -e + trap EXIT with idempotent restore + ::error:: annotation if restore itself fails.

@rafaelscosta
rafaelscosta merged commit 66b302a into main May 17, 2026
43 of 44 checks passed
@rafaelscosta
rafaelscosta deleted the fix/release-pipeline-hardening branch May 17, 2026 11:03
rafaelscosta added a commit that referenced this pull request May 17, 2026
…leshooting (#745)

* docs: align ecosystem with release-procedure SOP + Issue 10 troubleshooting

Cross-cutting cleanup so the operator/student/agent ecosystem references
the canonical release SOP (introduced in PR #744) consistently and
exposes the npm-hijack troubleshooting path to students.

## docs/guides/installation-troubleshooting.md

Added Issue 10: "Pro activation failed: Installed Pro artifact did not
create node_modules/@aiox-squads/pro" — the exact error that affected
cohorts for 30 days before PR #742 shipped. Documents symptom, root
cause (npm hijack into ancestor with package.json/workspaces), fix
(5.2.6+), and a two-tier recovery procedure (simple retry first, then
cache cleanup + residual-install removal if state is fouled).

## docs/guides/aiox-pro-access.md

"Erros comuns" section gains an explicit entry for the install failure
with a direct link to Issue 10 above. Previously the guide only covered
auth-side errors; students hitting the install-side error googled into
silence.

## .aiox-core/development/tasks/publish-npm.md
## .aiox-core/development/tasks/release-management.md

Both converted to slim wrappers that delegate to
docs/guides/release-procedure.md as the authoritative source. The full
procedures previously inlined here (757 and 257 lines respectively)
were missing the gotchas paid for in 11 patches across 30 days: two-
system branch protection bypass, publish race conditions, Windows path
escape in node -e, npm CDN propagation budget, payload sanitization
for ruleset PUT, trap EXIT for atomic restore. Maintaining the full
content in two files would have drifted immediately; the wrappers
keep the task IDs that agent workflows reference while pointing at
the one source of truth.

## .aiox-core/development/agents/devops.md

Added a "Release Procedure (NON-NEGOTIABLE Reference)" section to the
DevOps Guide. When @devops is invoked for a release, push, or publish
that ends with a tag push to @aiox-squads/*, the agent now has explicit
instruction to load and follow the SOP. Captures the institutional
memory items most likely to bite (two-system protection, 4-site version
bump, publish_legacy race, smoke timeout, Windows path escape) so the
agent does not re-discover them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit feedback on PR #745

Four real issues caught:

1. devops.md L584 referenced `*publish` which is not in the agent's
   declared commands list. Replaced with `*push` followed by version-bump
   intent, which matches the actual command surface.

2. release-management.md L43 + publish-npm.md L32 + release-procedure.md
   L170 had `git tag -a vX.Y.Z origin/main -m "<notes>"` — options mixed
   with positional args. Reordered to `git tag -a -m "<notes>" vX.Y.Z
   origin/main` (options before tag name, ref last) as the idiomatic
   form. Both forms work in modern git, but the linted form is more
   robust against argument parsing edge cases.

3. installation-troubleshooting.md L260: fenced output block was
   missing language tag — failing MD040 linter. Added `text`.

4. installation-troubleshooting.md L276 (CodeRabbit nitpick, addressed
   in full): the cleanup snippets were bash-only and assumed
   macOS/Linux. Students hitting the original error on Windows had no
   recovery path. Added labelled PowerShell and Command Prompt variants
   alongside the bash version, all three with equivalent behavior:
   purge stray `node_modules/@aiox-squads/pro` directories + clear
   the npx cache + retry the install with `-p @aiox-squads/core@latest`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rafaelscosta added a commit that referenced this pull request May 18, 2026
* chore(release): bump to 5.2.7 — consolidate PRs #744-#750 from 5.2.6

Consolidates 7 PRs merged after the 5.2.6 release. None of them
required an immediate hotfix on their own, but together they unblock
cohort students from a real CI/CD failure mode (#739 Bug 1) and
remove three sources of registry drift.

## What ships in 5.2.7

- **#744** (66b302a) — Pipeline hardening: `publish_legacy_aiox_core` race fix,
  smoke timeout 90s→240s with dual visibility check, structured notify summary,
  Windows path escape fix via WORKSPACE_DIR env var. Plus the canonical
  `docs/guides/release-procedure.md` SOP.
- **#745** (342ef63) — Ecosystem alignment: `installation-troubleshooting.md`
  Issue 10 (install-side recovery for the npm-hijack symptom), aiox-pro-access.md
  link, slim wrappers for publish-npm.md/release-management.md tasks pointing
  at the SOP, devops.md Release Procedure section.
- **#746** (ef7a352) — Internal `.aiox-core/package.json` rebranded
  `@aiox-fullstack/core@4.31.1` → `@aiox-squads/core-internal@5.2.7` (private,
  no phantom peerDeps). New `scripts/validate-aiox-core-namespace.js` (5-rule
  gate) wired into `validate:publish`. Closes #739 Bug 2.
- **#747** (e5cd355) — 70 substitutions across 17 files realigning legacy
  `aiox-core/{templates,checklists,agents,tasks,workflows,agent-teams}/<X>`
  references to current `.aiox-core/...` layout. Unblocks
  `@po *validate-story-draft`. Closes #741.
- **#748** (1f97f6f) — IDS `extractPurpose()` rewritten as strict priority
  chain (frontmatter → ## Purpose → ## Overview → # Title → fallback).
  Removed body-level regex that picked up example transcripts as
  purposes. New `syncSelfRegistryEntry()` writes sentinel
  `sha256:<self-reference>` checksum for the registry's own record.
- **#749** (70fa5e2) — `looksLikePlaceholder()` filter excludes 16 garbage
  purposes (`{Brief...}`, `{{TASK_TITLE}}`, `${context.componentName}`).
- **#750** (21782cc) — Installer honors `--ci` / `--yes` / `--skipPrompts`
  flags at the CLAUDE.md merge prompt. Non-interactive runs no longer block
  waiting for keyboard input. Closes #739 Bug 1.

## Version sites bumped (5 — up from 4 in the previous SOP)

- `package.json` (root): 5.2.6 → 5.2.7
- `compat/aiox-core/package.json`: version + dep["@aiox-squads/core"] 5.2.6 → 5.2.7
- `packages/installer/package.json`: 3.3.5 → 3.3.6 (#750 touched installer src)
- `.aiox-core/package.json` (internal): 5.2.6 → 5.2.7 (validate-aiox-core-namespace
  enforces lockstep with root — new in 5.2.7, first release under the gate)
- `package-lock.json` refresh
- `CHANGELOG.md`: new [5.2.7] entry
- `docs/guides/release-procedure.md`: bump the "4 sites" rule to "5 sites"
  reflecting the validate-aiox-core-namespace gate added in PR #746

## Validation

- `npm run lint` → exit 0
- `npm run test:ci` → 8510 passed (151 skipped), 344/355 suites, 229s
- `npx jest tests/installer/ --no-coverage` → 276/276 pass
- `npm run validate:publish` → PASS (12911 files; namespace sync verified)

Pro install hijack fix (#742) shipped in 5.2.6 and remains in production —
this release does not re-iterate it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: address CodeRabbit nitpick on release-procedure.md lockstep wording

PR #751 CodeRabbit review flagged that the "Bump all five version sites in
lockstep" wording was ambiguous — the table below explicitly treats
`packages/installer/package.json` as conditional ("patch bump if installer
changed; otherwise leave"). Operators reading only line 36 could over-bump
the installer.

Reworded to distinguish:
- Root + internal manifest + compat: MUST stay in lockstep at same version
- packages/installer: bumped ONLY when installer source changed (not every release)

Also explicit on the two failure-mode detection layers:
- Smoke tests (catch silent publish mismatches)
- validate-aiox-core-namespace.js wired into validate:publish (rule 4 enforces lockstep)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tuanmedeiros pushed a commit to tuanmedeiros/aios-core-synkraay that referenced this pull request Jun 2, 2026
…kraAI#744)

* fix(release): harden npm-publish pipeline + document release SOP

Three real failure modes observed during the v5.2.5/5.2.6 releases, each
fixed and documented so the next release does not re-discover them.

## npm-publish.yml

### Race: publish_legacy_aiox_core ran in parallel with publish

The legacy compat wrapper `aiox-core@X.Y.Z` declares
`@aiox-squads/core@X.Y.Z` as a runtime dependency. The smoke test step
runs `npx --yes aiox-core@X.Y.Z --version`, which transitively installs
the scoped package. With both jobs in parallel, `npx` raced against npm
CDN propagation of the scoped package and timed out after 90s — making
the entire job appear failed even though the publish step inside it
had already printed "✅ Published aiox-core@X.Y.Z".

Fix: `publish_legacy_aiox_core` now `needs: [test, build, publish]`,
serializing the legacy publish behind the scoped publish.

### Smoke test legacy npx: timeout too short, no diagnostics

Bumped retry budget 6×15s (90s) → 16×15s (240s). Added a second
visibility check for the scoped dependency before invoking `npx` so we
distinguish "compat not visible" from "scoped dep not visible". Captured
last `npx` stderr in the timeout message instead of failing silently.

### Notify: aggregated bool hid which step failed

Replaced with a structured summary that distinguishes hard fail (scoped
publish) from soft warnings (workspace, legacy, smoke). Only the scoped
publish is treated as release-blocking; the rest are informational and
can be republished out-of-band.

## installer-smoke-matrix.yml

### Windows path mangling in node -e via Git Bash

`${{ github.workspace }}` on a Windows runner expands to
`D:\a\aiox-core\aiox-core`. Interpolated into a `node -e "..."` script
running on Git Bash, the backslashes were interpreted as escape
sequences (`\a` → `a`), producing `D:aaiox-coreaiox-core` and
`Cannot find module ...`.

Fix: pass workspace path via `WORKSPACE_DIR` env var, read inside the
script as `process.env.WORKSPACE_DIR`. Applied to all 3 smoke install
steps.

## docs/guides/release-procedure.md

New SOP capturing the entire release pipeline:
- Pre-flight checks (lint, tests, tokens)
- 4-site version bump coordination (root, compat, installer, lock)
- Two-system branch protection bypass dance (modern ruleset +
  legacy branch protection both enforce CODEOWNERS independently;
  `gh pr merge --admin` bypasses neither in isolation)
- Atomic bypass→merge→restore as one shell block
- Tag-driven publish trigger
- Post-publish verification (registry sanity + artifact inspection)
- Known CI quirks with mitigations
- Rollback procedure (deprecate, don't unpublish)
- Failure modes seen in production with root causes

The SOP cites real PR numbers and incidents so future operators know
why each step exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit feedback on PR SynkraAI#744

Two real bugs caught:

## notify step printed "Successfully published" for skipped scoped publish

The previous condition gated the success block on
`PUBLISH_RESULT = success OR skipped` AND pre-seeded the PUBLISHED array
with `@aiox-squads/core@${VERSION}` unconditionally. Result: a workflow
run where `publish` was skipped (e.g. manual dispatch excluding `core`)
would falsely report the scoped package as published.

Fix: PUBLISHED is now built only from jobs that actually succeeded.
A separate branch handles the all-skipped case explicitly. The
success message only renders when something was actually published.

## SOP bypass dance: snapshot reuse could leave protections relaxed

The documented procedure piped the raw `gh api .../rulesets/...`
output back into `gh api -X PUT`. The raw response includes read-only
fields (`id`, `node_id`, `created_at`, `updated_at`, `_links`, `url`)
that GitHub rejects with HTTP 422 on PUT — meaning the restore could
silently fail and leave production in a relaxed state.

Two fixes layered for safety:

1. Snapshot is now separated from restore payload. Raw snapshot stays
   on disk only as a diff baseline. Sanitized restore payload is
   derived via `jq '{name, target, enforcement, conditions,
   bypass_actors, rules}'` — only writable fields.

2. The bypass→merge→restore sequence is wrapped in `set -e` + a
   `trap 'restore_protections' EXIT`. The trap fires on ANY exit
   (success, failure, signal) and idempotently re-applies the
   sanitized restore payloads. If a restore step itself fails, the
   trap emits `::error::` annotations so the failure surfaces in the
   GitHub Actions UI.

The previous documentation worked in practice because `gh api --input`
tolerates read-only fields in some paths, but that behavior is not
guaranteed. The new procedure is defensively correct under network
failures, manual interrupts, and edge-case API rejections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tuanmedeiros pushed a commit to tuanmedeiros/aios-core-synkraay that referenced this pull request Jun 2, 2026
…leshooting (SynkraAI#745)

* docs: align ecosystem with release-procedure SOP + Issue 10 troubleshooting

Cross-cutting cleanup so the operator/student/agent ecosystem references
the canonical release SOP (introduced in PR SynkraAI#744) consistently and
exposes the npm-hijack troubleshooting path to students.

## docs/guides/installation-troubleshooting.md

Added Issue 10: "Pro activation failed: Installed Pro artifact did not
create node_modules/@aiox-squads/pro" — the exact error that affected
cohorts for 30 days before PR SynkraAI#742 shipped. Documents symptom, root
cause (npm hijack into ancestor with package.json/workspaces), fix
(5.2.6+), and a two-tier recovery procedure (simple retry first, then
cache cleanup + residual-install removal if state is fouled).

## docs/guides/aiox-pro-access.md

"Erros comuns" section gains an explicit entry for the install failure
with a direct link to Issue 10 above. Previously the guide only covered
auth-side errors; students hitting the install-side error googled into
silence.

## .aiox-core/development/tasks/publish-npm.md
## .aiox-core/development/tasks/release-management.md

Both converted to slim wrappers that delegate to
docs/guides/release-procedure.md as the authoritative source. The full
procedures previously inlined here (757 and 257 lines respectively)
were missing the gotchas paid for in 11 patches across 30 days: two-
system branch protection bypass, publish race conditions, Windows path
escape in node -e, npm CDN propagation budget, payload sanitization
for ruleset PUT, trap EXIT for atomic restore. Maintaining the full
content in two files would have drifted immediately; the wrappers
keep the task IDs that agent workflows reference while pointing at
the one source of truth.

## .aiox-core/development/agents/devops.md

Added a "Release Procedure (NON-NEGOTIABLE Reference)" section to the
DevOps Guide. When @devops is invoked for a release, push, or publish
that ends with a tag push to @aiox-squads/*, the agent now has explicit
instruction to load and follow the SOP. Captures the institutional
memory items most likely to bite (two-system protection, 4-site version
bump, publish_legacy race, smoke timeout, Windows path escape) so the
agent does not re-discover them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit feedback on PR SynkraAI#745

Four real issues caught:

1. devops.md L584 referenced `*publish` which is not in the agent's
   declared commands list. Replaced with `*push` followed by version-bump
   intent, which matches the actual command surface.

2. release-management.md L43 + publish-npm.md L32 + release-procedure.md
   L170 had `git tag -a vX.Y.Z origin/main -m "<notes>"` — options mixed
   with positional args. Reordered to `git tag -a -m "<notes>" vX.Y.Z
   origin/main` (options before tag name, ref last) as the idiomatic
   form. Both forms work in modern git, but the linted form is more
   robust against argument parsing edge cases.

3. installation-troubleshooting.md L260: fenced output block was
   missing language tag — failing MD040 linter. Added `text`.

4. installation-troubleshooting.md L276 (CodeRabbit nitpick, addressed
   in full): the cleanup snippets were bash-only and assumed
   macOS/Linux. Students hitting the original error on Windows had no
   recovery path. Added labelled PowerShell and Command Prompt variants
   alongside the bash version, all three with equivalent behavior:
   purge stray `node_modules/@aiox-squads/pro` directories + clear
   the npx cache + retry the install with `-p @aiox-squads/core@latest`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tuanmedeiros pushed a commit to tuanmedeiros/aios-core-synkraay that referenced this pull request Jun 2, 2026
…m 5.2.6 (SynkraAI#751)

* chore(release): bump to 5.2.7 — consolidate PRs SynkraAI#744-SynkraAI#750 from 5.2.6

Consolidates 7 PRs merged after the 5.2.6 release. None of them
required an immediate hotfix on their own, but together they unblock
cohort students from a real CI/CD failure mode (SynkraAI#739 Bug 1) and
remove three sources of registry drift.

## What ships in 5.2.7

- **SynkraAI#744** (d949918) — Pipeline hardening: `publish_legacy_aiox_core` race fix,
  smoke timeout 90s→240s with dual visibility check, structured notify summary,
  Windows path escape fix via WORKSPACE_DIR env var. Plus the canonical
  `docs/guides/release-procedure.md` SOP.
- **SynkraAI#745** (b728a39) — Ecosystem alignment: `installation-troubleshooting.md`
  Issue 10 (install-side recovery for the npm-hijack symptom), aiox-pro-access.md
  link, slim wrappers for publish-npm.md/release-management.md tasks pointing
  at the SOP, devops.md Release Procedure section.
- **SynkraAI#746** (dea939e) — Internal `.aiox-core/package.json` rebranded
  `@aiox-fullstack/core@4.31.1` → `@aiox-squads/core-internal@5.2.7` (private,
  no phantom peerDeps). New `scripts/validate-aiox-core-namespace.js` (5-rule
  gate) wired into `validate:publish`. Closes SynkraAI#739 Bug 2.
- **SynkraAI#747** (e874103) — 70 substitutions across 17 files realigning legacy
  `aiox-core/{templates,checklists,agents,tasks,workflows,agent-teams}/<X>`
  references to current `.aiox-core/...` layout. Unblocks
  `@po *validate-story-draft`. Closes SynkraAI#741.
- **SynkraAI#748** (e8f8761) — IDS `extractPurpose()` rewritten as strict priority
  chain (frontmatter → ## Purpose → ## Overview → # Title → fallback).
  Removed body-level regex that picked up example transcripts as
  purposes. New `syncSelfRegistryEntry()` writes sentinel
  `sha256:<self-reference>` checksum for the registry's own record.
- **SynkraAI#749** (6113083) — `looksLikePlaceholder()` filter excludes 16 garbage
  purposes (`{Brief...}`, `{{TASK_TITLE}}`, `${context.componentName}`).
- **SynkraAI#750** (1d16b51) — Installer honors `--ci` / `--yes` / `--skipPrompts`
  flags at the CLAUDE.md merge prompt. Non-interactive runs no longer block
  waiting for keyboard input. Closes SynkraAI#739 Bug 1.

## Version sites bumped (5 — up from 4 in the previous SOP)

- `package.json` (root): 5.2.6 → 5.2.7
- `compat/aiox-core/package.json`: version + dep["@aiox-squads/core"] 5.2.6 → 5.2.7
- `packages/installer/package.json`: 3.3.5 → 3.3.6 (SynkraAI#750 touched installer src)
- `.aiox-core/package.json` (internal): 5.2.6 → 5.2.7 (validate-aiox-core-namespace
  enforces lockstep with root — new in 5.2.7, first release under the gate)
- `package-lock.json` refresh
- `CHANGELOG.md`: new [5.2.7] entry
- `docs/guides/release-procedure.md`: bump the "4 sites" rule to "5 sites"
  reflecting the validate-aiox-core-namespace gate added in PR SynkraAI#746

## Validation

- `npm run lint` → exit 0
- `npm run test:ci` → 8510 passed (151 skipped), 344/355 suites, 229s
- `npx jest tests/installer/ --no-coverage` → 276/276 pass
- `npm run validate:publish` → PASS (12911 files; namespace sync verified)

Pro install hijack fix (SynkraAI#742) shipped in 5.2.6 and remains in production —
this release does not re-iterate it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: address CodeRabbit nitpick on release-procedure.md lockstep wording

PR SynkraAI#751 CodeRabbit review flagged that the "Bump all five version sites in
lockstep" wording was ambiguous — the table below explicitly treats
`packages/installer/package.json` as conditional ("patch bump if installer
changed; otherwise leave"). Operators reading only line 36 could over-bump
the installer.

Reworded to distinguish:
- Root + internal manifest + compat: MUST stay in lockstep at same version
- packages/installer: bumped ONLY when installer source changed (not every release)

Also explicit on the two failure-mode detection layers:
- Smoke tests (catch silent publish mismatches)
- validate-aiox-core-namespace.js wired into validate:publish (rule 4 enforces lockstep)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: devops CI/CD, GitHub Actions (.github/) area: docs Documentation (docs/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant