mandrel-platform 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,34 @@
1
1
  # mandrel-platform
2
2
 
3
- Shared CI/deploy workflows, composite toolchain action, npm config package,
4
- Renovate preset, and operator runbook templates for the Mandrel platform.
3
+ The **single source of truth (SSOT)** for CI/CD, security, and configuration
4
+ across the Mandrel fleet. A consumer repo adopts this platform once and inherits
5
+ a converged, hardened baseline — reusable GitHub Actions workflows, a composite
6
+ toolchain action, importable config bases, guardrail scripts, edge-security
7
+ middleware, a Renovate preset, and operator runbook templates — instead of
8
+ hand-maintaining its own copies and drifting apart over time.
9
+
10
+ ## What's in scope
11
+
12
+ | Pillar | What it provides |
13
+ | ------ | ---------------- |
14
+ | **Reusable workflows** | `workflow_call` CI, deploy, secret-scan, release, and CodeQL pipelines, consumed by tag/SHA pin. |
15
+ | **Composite action** | `setup-toolchain` — pnpm + Node + frozen install in one step. |
16
+ | **Config bases (npm)** | `extends`-able baselines: TypeScript, Biome, Knip, Stryker, commitlint, dependency-cruiser, size-limit, Lighthouse. |
17
+ | **Edge-security middleware (npm)** | Per-env closed-allowlist CORS, security headers, and app-layer rate limiting for Astro + Hono. |
18
+ | **Guardrail scripts (npm)** | Dependency-free policy checks: CVE gate, action-pin ratchet, coverage floor, destructive-migration guard, workflow-portability, required-contexts, docs-staleness. |
19
+ | **Renovate preset** | Shared dependency-update policy, including auto-bumping this repo's own `uses:` pins. |
20
+ | **Supply-chain config** | pnpm-native hardening fragment (`blockExoticSubdeps`, `trustPolicy`, 7-day `minimumReleaseAge`). |
21
+ | **Adoption & drift control** | `platform-sync` (adopt/repair) plus a scheduled cross-consumer pin-drift dashboard and auto-repair-PR loop. |
22
+ | **Runbook templates** | Copyable thin-stub operator runbooks that link back to the canonical process docs. |
23
+
24
+ **Out of scope.** mandrel-platform is not an application and ships no runtime
25
+ service — it deploys nothing of its own beyond its release train. Consumers keep
26
+ every project-specific knob (entrypoints, budgets, score floors, deploy targets)
27
+ local: the platform sets the floor, the consumer sets the ceiling. The `.agents/`
28
+ tree in this repo is the Mandrel agent framework used to *develop* the platform
29
+ (sourced from the separate `mandrel` CLI) — it is dev-time only and is **not**
30
+ part of the published npm package, whose `files` allowlist ships only `config/`,
31
+ `default.json`, `scripts/`, and `templates/`.
5
32
 
6
33
  **Docs:** [reusable-workflows.md](docs/reusable-workflows.md) (the `workflow_call`
7
34
  contract) · [decisions.md](docs/decisions.md) (decision log). Status, the
@@ -11,15 +38,24 @@ consumer convergence matrix, and the forward roadmap are tracked privately.
11
38
 
12
39
  ## Reusable workflows
13
40
 
14
- The shared `workflow_call` workflows `pr-quality.yml` and
15
- `deploy-cloudflare.yml` (plus `secret-scan-push.yml`, `release-automation.yml`,
16
- `codeql.yml`, and `smoke-dispatch.yml`) — and their public input/secret
17
- contract are documented in
18
- **[docs/reusable-workflows.md](docs/reusable-workflows.md)**. Consumers should
19
- configure their callers from that reference (input types, defaults,
20
- when-to-override, the frozen `{CLOUDFLARE_*, TURSO_*}` deploy secret allowlist,
21
- the single `ci-required` aggregator context, and the pin-by-tag/SHA versioning
22
- model).
41
+ Five workflows expose a stable `workflow_call` contract and are consumed by
42
+ tag/SHA pin. Configure your callers from
43
+ **[docs/reusable-workflows.md](docs/reusable-workflows.md)**the authoritative
44
+ reference for input types, defaults, when-to-override, the frozen
45
+ `{CLOUDFLARE_*, TURSO_*}` deploy-secret allowlist, the single `ci-required`
46
+ aggregator context, and the pin-by-tag/SHA versioning model.
47
+
48
+ | Workflow | Purpose |
49
+ | -------- | ------- |
50
+ | [`pr-quality.yml`](docs/reusable-workflows.md#pr-qualityyml) | Tiered PR gate — lint/format → typecheck → unit → contract → e2e/smoke → migration-guard → security → osv-scan, each tier independently toggled, behind one `ci-required` aggregator. |
51
+ | [`deploy-cloudflare.yml`](docs/reusable-workflows.md#deploy-cloudflareyml) | Defence-in-depth Cloudflare deploy with a frozen deploy-secret allowlist. |
52
+ | [`secret-scan-push.yml`](docs/reusable-workflows.md#secret-scan-pushyml) | Full-history gitleaks secret scan on push to the default branch. |
53
+ | [`release-automation.yml`](docs/reusable-workflows.md#release-automationyml) | Conventional-commit release lifecycle (version bump + `CHANGELOG.md` + tag) via release-please. |
54
+ | [`codeql.yml`](docs/reusable-workflows.md#codeqlyml) | CodeQL SAST analysis — dual-mode: runs on this repo's push/PR/schedule **and** is `workflow_call`-consumable. |
55
+
56
+ > `smoke-dispatch.yml` is a **platform-internal** cross-repo smoke trigger
57
+ > (`push` / `workflow_dispatch`, not `workflow_call`). It appears in the
58
+ > reference for completeness but is not part of the consumer caller surface.
23
59
 
24
60
  `release-automation.yml` extends the platform from CI/deploy into the **full
25
61
  release lifecycle**: a thin caller gets conventional-commit-driven version
@@ -147,6 +183,30 @@ mutate set (`stryker.config.json`):
147
183
  }
148
184
  ```
149
185
 
186
+ #### `commitlint.base.mjs`
187
+
188
+ Single-sources the conventional-commit **type-enum** — the eleven types
189
+ (`feat`, `fix`, `perf`, `refactor`, `revert`, `docs`, `style`, `chore`,
190
+ `test`, `build`, `ci`) documented in
191
+ [`.agents/rules/git-conventions.md`](.agents/rules/git-conventions.md) — so
192
+ consumers stop hand-copying the list into their own
193
+ `commitlint.config.js`. Extends `@commitlint/config-conventional` for
194
+ everything else (header casing/length, body/footer blank-line rules) and
195
+ narrows `type-enum` to the fleet list. commitlint supports a native
196
+ `extends`, so a consumer's local config reduces to the extend plus any
197
+ repo-specific scope enforcement (`commitlint.config.js`):
198
+
199
+ ```js
200
+ export default {
201
+ extends: ["mandrel-platform/commitlint.base.mjs"],
202
+ // repo-specific scope-enum, etc. — optional
203
+ };
204
+ ```
205
+
206
+ Keep this base's `type-enum`, the git-conventions.md prose list, and
207
+ `release-please-config.json`'s `changelog-sections` in sync when adding a
208
+ type — all three must agree.
209
+
150
210
  #### `dependency-cruiser.base.json`
151
211
 
152
212
  Shared dependency-cruiser rule set (no-circular, no-orphans,
@@ -362,6 +422,78 @@ is detected by Renovate).
362
422
 
363
423
  ---
364
424
 
425
+ ### Guardrail & policy-check scripts
426
+
427
+ Beyond the CVE gate, the package ships a set of **dependency-free guardrail
428
+ lints** (`node`-only, nothing to install) that enforce cross-repo CI and
429
+ security invariants. Several are already wired into the reusable workflows, so a
430
+ consumer that adopts those inherits the check for free; each is also runnable
431
+ standalone (`node node_modules/mandrel-platform/scripts/<name>.mjs`) or copyable
432
+ into a repo's own `scripts/`.
433
+
434
+ | Script | Enforces | Wired into |
435
+ | ------ | -------- | ---------- |
436
+ | `check-coverage-threshold.mjs` | Coverage floor (lines/statements/functions/branches) read from `coverage-summary.json`; threshold `0` disables the gate. | `pr-quality.yml` (unit tier) |
437
+ | `check-destructive-migration.mjs` | Blocks `DROP` / `TRUNCATE` / `ALTER … DROP` (and Drizzle `.dropTable()`) in migration files unless a reviewer applies the override label. | `pr-quality.yml` (migration-guard tier, opt-in) |
438
+ | `check-workflow-portability.mjs` | Catches cross-repo footguns in reusable workflows / composite actions: relative `uses:` paths, `${{ }}` in `workflow_call` input metadata, and lagging first-party pins. | `pr-quality.yml` + `ci.yml` |
439
+ | `check-action-pins.mjs` | Ratchet requiring every third-party Action to be pinned to a full 40-char commit SHA (tag-swap defence); local and first-party refs are exempt. | `ci.yml` |
440
+ | `check-required-contexts.mjs` | Validates that every branch-protection required check in `main-protection.json` maps to a real CI job — no phantom required checks. Also **warns** (never blocks) when the caller file / display `name:` / caller job id diverge from the canonical `ci.yml` / `CI` / `ci` triplet ([details](docs/reusable-workflows.md#canonical-caller-naming-the-ciyml--ci--ci-triplet)). | `ci.yml` |
441
+ | `check-docs-staleness.mjs` | Lints markdown/JSON docs for known staleness patterns (stale URLs, expired dates, dead runbook paths); suppressible per-rule. | standalone |
442
+ | `check-repo-settings.mjs` | Cross-consumer dashboard for the GitHub-side repo-settings baseline (merge methods, squash source, auto-merge, Actions default token permissions, PR-approval-by-Actions) — see below. | standalone / `platform-sync --check-settings` |
443
+
444
+ > **`config/main-protection.schema.json`** is the JSON Schema for the
445
+ > branch-protection contract (`docs/runbooks/main-protection.json`) — required
446
+ > status checks, the aggregator job, upstream jobs, and enforcement flags.
447
+ > `check-required-contexts.mjs` validates the contract against the actual
448
+ > workflow job graph; see the
449
+ > [branch-protection runbook](docs/runbooks/branch-protection-setup.md).
450
+
451
+ > **`config/repo-settings.schema.json`** is the JSON Schema for the
452
+ > repo-settings baseline contract (`docs/runbooks/repo-settings.json`),
453
+ > sibling to `main-protection.schema.json` — merge methods, squash-commit
454
+ > source, auto-merge/delete-branch-on-merge, Actions default workflow token
455
+ > permissions, and whether Actions can approve pull requests. Fleet baseline
456
+ > decided 2026-07-01 (see `docs/decisions.md`): squash-only merges, squash
457
+ > source `PR_TITLE`/`PR_BODY`, auto-merge + delete-branch-on-merge on, Actions
458
+ > default token permissions `read`, `can_approve_pull_request_reviews` off.
459
+ >
460
+ > **Because `squashMergeCommitMessage` is `PR_BODY`, PR templates must stay
461
+ > commit-body-safe.** The PR description becomes the literal squash-commit
462
+ > body on `main`, which `release-please` and `commitlint` then parse — a
463
+ > template that injects checklist boilerplate, HTML comments, or
464
+ > non-conventional-commit prose into that body will land in commit history and
465
+ > can break changelog generation or commitlint's body rules. Keep PR templates
466
+ > to short, commit-message-safe prose, or put checklists in sections authors
467
+ > delete before merge.
468
+ >
469
+ > **Check + apply.** `scripts/check-repo-settings.mjs` is the GitHub-side
470
+ > drift dashboard (mirrors `check-pin-drift.mjs`'s shape: data-driven consumer
471
+ > registry — reuses `scripts/pin-drift-consumers.json` — injectable `gh`
472
+ > runner, `--json`/`--strict`). `scripts/platform-sync.mjs` gained a
473
+ > settings mode for the per-consumer check/apply flow:
474
+ >
475
+ > ```bash
476
+ > # Report drift for one consumer against the baseline — never mutates, never
477
+ > # fails the exit code unless the read itself errors (standing decision #10).
478
+ > node scripts/platform-sync.mjs --check-settings --consumer-repo dsj1984/domio
479
+ >
480
+ > # Same read, then PATCH the drifted fields to match the baseline.
481
+ > node scripts/platform-sync.mjs --apply-settings --consumer-repo dsj1984/domio
482
+ >
483
+ > # Preview what --apply-settings would PATCH without mutating anything.
484
+ > node scripts/platform-sync.mjs --apply-settings --dry-run --consumer-repo dsj1984/domio
485
+ > ```
486
+ >
487
+ > Both commands accept `--baseline <path>` (default:
488
+ > `docs/runbooks/repo-settings.json`) and `--json` for a machine-readable
489
+ > envelope. **Non-blocking by design** (standing decision #10, same posture as
490
+ > the pin-drift dashboard and `check-ruleset.mjs`): drift is reported, not a
491
+ > hard gate — it never fails CI on a consumer's `main`. Branch-protection
492
+ > ruleset drift is out of scope here (see the companion `check-ruleset.mjs`
493
+ > story); this contract covers repo-settings only.
494
+
495
+ ---
496
+
365
497
  ## Renovate preset
366
498
 
367
499
  The shared Renovate preset (`default.json`, also exposed at
@@ -464,12 +596,27 @@ reports `already in sync`):
464
596
  actions (`actions/checkout`, …) are left untouched. The trailing
465
597
  `# <ref>` comment is refreshed so the pin stays human-auditable and the
466
598
  Renovate auto-bump rule above can track it.
467
- 2. **Materializes runbook reference stubs** (§2.2 *link, don't copy*). Copies
599
+ 2. **Checks CI-caller naming** (advisory only, Story #173). Reports whether
600
+ `.github/workflows/ci.yml` matches the canonical caller triplet — file
601
+ `ci.yml`, display name `CI`, caller job id `ci` (required context
602
+ `ci / ci-required`; see
603
+ [reusable-workflows.md § "Canonical caller naming"](docs/reusable-workflows.md#canonical-caller-naming-the-ciyml--ci--ci-triplet)).
604
+ Never renames or rewrites anything — a caller rename must land atomically
605
+ with its own branch-protection ruleset context update, which is a
606
+ deliberate per-consumer Story, not an automatic sync side-effect.
607
+ 3. **Materializes runbook reference stubs** (§2.2 *link, don't copy*). Copies
468
608
  the thin stubs from `templates/runbooks/` into the consumer's
469
609
  `docs/runbooks/` **only when absent** — an already-adopted stub is skipped,
470
610
  and a full local copy (no stub marker) is surfaced as a warning to
471
611
  reconcile by hand, never silently overwritten.
472
- 3. **Reconciles `extends`.** Prepends `github>dsj1984/mandrel-platform` to the
612
+ 4. **Materializes workflow caller templates.** Copies canonical callers from
613
+ `templates/workflows/` (e.g. `deploy-staging.yml`, the one-paved-road
614
+ `workflow_run` caller for `deploy-cloudflare.yml`'s CI-green guard — see
615
+ [`docs/reusable-workflows.md`](docs/reusable-workflows.md#deploy-cloudflareyml))
616
+ into the consumer's `.github/workflows/` — same link-don't-copy semantics:
617
+ only when absent, and a hand-authored file without the template marker is
618
+ surfaced as a warning rather than overwritten.
619
+ 5. **Reconciles `extends`.** Prepends `github>dsj1984/mandrel-platform` to the
473
620
  consumer's Renovate `extends` and `mandrel-platform/tsconfig.base.json` to
474
621
  its `tsconfig.json` `extends`. The SSOT goes first so the consumer's own
475
622
  later entries continue to override it.
@@ -481,16 +628,74 @@ result envelope on stdout).
481
628
 
482
629
  ---
483
630
 
631
+ ## Drift control & auto-repair
632
+
633
+ The platform actively keeps consumers converged rather than trusting them to
634
+ stay in sync by hand. Two scheduled workflows run this loop against the consumer
635
+ registry in
636
+ [`scripts/pin-drift-consumers.json`](scripts/pin-drift-consumers.json):
637
+
638
+ - **Detect** — [`pin-drift.yml`](.github/workflows/pin-drift.yml) (weekly +
639
+ `workflow_dispatch`) runs `check-pin-drift.mjs`, a cross-consumer dashboard
640
+ that flags split pins (multiple mandrel-platform SHAs in one repo), release
641
+ lag, npm lag, and npm-vs-workflow surface skew. Advisory by default; `--strict`
642
+ turns drift into a failure. See the
643
+ [pin-drift dashboard runbook](docs/runbooks/pin-drift-dashboard.md).
644
+ - **Repair** — [`platform-sync-repair.yml`](.github/workflows/platform-sync-repair.yml)
645
+ runs `platform-repair.mjs`, which clones each drifting consumer, runs
646
+ `platform-sync`, and opens (or updates) an **idempotent** repair PR on a stable
647
+ head branch. Requires a fine-grained `PIN_REPAIR_TOKEN` scoped to the consumer
648
+ repos' contents + pull-requests.
649
+
650
+ `update-semgrep-rules.mjs` is a related maintenance script that vendors Semgrep's
651
+ `p/default` ruleset — filtered to the languages actually in-repo — into
652
+ `.semgrep/rules.json` against a pinned Semgrep version, so the SAST step in
653
+ `pr-quality.yml` scans deterministically. Run it deliberately when bumping the
654
+ ruleset, not on every CI run.
655
+
656
+ ---
657
+
658
+ ## Runbook templates
659
+
660
+ `templates/runbooks/` ships **copyable thin-stub** operator runbooks — one per
661
+ canonical runbook in [`docs/runbooks/`](docs/runbooks). The adoption model is
662
+ *link, don't copy*: each stub links to the canonical process doc (the source of
663
+ truth) and carries only `<PLACEHOLDER>` slots for project-specific values, so an
664
+ upstream process change is picked up by re-reading the link rather than
665
+ re-authoring the stub. `platform-sync` materializes them for you — link-only, and
666
+ never clobbering a stub you have already filled in.
667
+
668
+ | Stub | Canonical runbook |
669
+ | ---- | ----------------- |
670
+ | `deploy-promotion.md` | staging → production promotion |
671
+ | `incident-response.md` | severity, escalation, postmortem |
672
+ | `database-backup-restore.md` | backup, PITR, restore/rollback |
673
+ | `observability.md` | logs, Sentry, uptime, metrics |
674
+ | `post-deploy-smoke.md` | boot-smoke gate + diagnosis |
675
+ | `environments-provisioning.md` | env model + provisioning steps |
676
+ | `dependency-update.md` | Renovate, CVE gate, catalog |
677
+ | `branch-protection-setup.md` | aggregator required-check model |
678
+
679
+ ---
680
+
484
681
  ## Development
485
682
 
486
683
  ```bash
487
- # Install dependencies
684
+ # Install dependencies (packageManager: pnpm@11.5.2)
488
685
  pnpm install
489
686
 
490
687
  # Bootstrap agent scaffolding
491
688
  pnpm run bootstrap
689
+
690
+ # Run the guardrail-script test suite (node:test)
691
+ pnpm test
492
692
  ```
493
693
 
694
+ Every script under `scripts/` (the guardrail lints, `platform-sync`,
695
+ `platform-repair`, `update-semgrep-rules`) carries a colocated `*.test.mjs`
696
+ suite run by `pnpm test`. The `.agents/` tree is the Mandrel agent framework this
697
+ repo is developed with — dev-time only, and not shipped in the npm package.
698
+
494
699
  ---
495
700
 
496
701
  ## Package exports
@@ -501,6 +706,7 @@ pnpm run bootstrap
501
706
  | `mandrel-platform/biome.base.json` | `config/biome.base.json` |
502
707
  | `mandrel-platform/knip.base.json` | `config/knip.base.json` |
503
708
  | `mandrel-platform/stryker.base.json` | `config/stryker.base.json` |
709
+ | `mandrel-platform/commitlint.base.mjs` | `config/commitlint.base.mjs` |
504
710
  | `mandrel-platform/dependency-cruiser.base.json` | `config/dependency-cruiser.base.json` |
505
711
  | `mandrel-platform/size-limit.base.json` | `config/size-limit.base.json` |
506
712
  | `mandrel-platform/lighthouse.base.json` | `config/lighthouse.base.json` |
@@ -0,0 +1,36 @@
1
+ /**
2
+ * commitlint.base.mjs — shared commitlint config for mandrel-platform
3
+ * consumers.
4
+ *
5
+ * Single-sources the conventional-commit type-enum from
6
+ * `.agents/rules/git-conventions.md` (§ Conventional Commits) so the eleven
7
+ * allowed types live in exactly one place instead of being hand-copied into
8
+ * each consumer's own `commitlint.config.js`. Keep this list, the
9
+ * git-conventions.md prose list, and `release-please-config.json`'s
10
+ * `changelog-sections` in sync when adding a type — all three must agree.
11
+ *
12
+ * Extends `@commitlint/config-conventional` for everything else (header
13
+ * casing/length, body/footer leading-blank-line, etc.) and narrows
14
+ * `type-enum` to the fleet's eleven types.
15
+ */
16
+
17
+ const TYPE_ENUM = [
18
+ "feat",
19
+ "fix",
20
+ "perf",
21
+ "refactor",
22
+ "revert",
23
+ "docs",
24
+ "style",
25
+ "chore",
26
+ "test",
27
+ "build",
28
+ "ci",
29
+ ];
30
+
31
+ export default {
32
+ extends: ["@commitlint/config-conventional"],
33
+ rules: {
34
+ "type-enum": [2, "always", TYPE_ENUM],
35
+ },
36
+ };
@@ -0,0 +1,78 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/dsj1984/mandrel-platform/blob/main/config/repo-settings.schema.json",
4
+ "title": "Repository Settings Baseline Contract",
5
+ "description": "Defines the GitHub-side repository settings baseline (merge methods, squash-commit source, auto-merge, Actions default token permissions, PR-approval-by-Actions) the fleet must converge on. Validated by scripts/check-repo-settings.mjs against each consumer's live settings (gh api repos/{owner}/{repo}); applied where safe by scripts/platform-sync.mjs --apply-settings. Non-blocking by design (standing decision #10) — drift is reported, never a hard gate on a consumer's main.",
6
+ "type": "object",
7
+ "required": [
8
+ "allowSquashMerge",
9
+ "allowMergeCommit",
10
+ "allowRebaseMerge",
11
+ "squashMergeCommitTitle",
12
+ "squashMergeCommitMessage",
13
+ "deleteBranchOnMerge",
14
+ "allowAutoMerge",
15
+ "actionsDefaultWorkflowPermissions",
16
+ "actionsCanApprovePullRequestReviews"
17
+ ],
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "$schema": {
21
+ "type": "string",
22
+ "description": "JSON Schema reference."
23
+ },
24
+ "allowSquashMerge": {
25
+ "type": "boolean",
26
+ "description": "Whether squash merging is allowed. The fleet baseline requires this true (squash-only).",
27
+ "default": true
28
+ },
29
+ "allowMergeCommit": {
30
+ "type": "boolean",
31
+ "description": "Whether merge commits are allowed. The fleet baseline requires this false (squash-only).",
32
+ "default": false
33
+ },
34
+ "allowRebaseMerge": {
35
+ "type": "boolean",
36
+ "description": "Whether rebase merging is allowed. The fleet baseline requires this false (squash-only).",
37
+ "default": false
38
+ },
39
+ "squashMergeCommitTitle": {
40
+ "type": "string",
41
+ "description": "Source of the squash-commit title.",
42
+ "enum": ["PR_TITLE", "COMMIT_OR_PR_TITLE"],
43
+ "default": "PR_TITLE"
44
+ },
45
+ "squashMergeCommitMessage": {
46
+ "type": "string",
47
+ "description": "Source of the squash-commit message body. The fleet baseline requires PR_BODY — commits landing on main feed release-please + commitlint, so the PR body becomes the literal commit body and must stay commit-body-safe (see the _note below).",
48
+ "enum": ["PR_BODY", "COMMIT_MESSAGES", "BLANK"],
49
+ "default": "PR_BODY"
50
+ },
51
+ "deleteBranchOnMerge": {
52
+ "type": "boolean",
53
+ "description": "Whether head branches are auto-deleted after a merge.",
54
+ "default": true
55
+ },
56
+ "allowAutoMerge": {
57
+ "type": "boolean",
58
+ "description": "Whether GitHub's native auto-merge feature is enabled for the repo.",
59
+ "default": true
60
+ },
61
+ "actionsDefaultWorkflowPermissions": {
62
+ "type": "string",
63
+ "description": "Default GITHUB_TOKEN permissions granted to Actions workflows. The fleet baseline requires 'read' (least-privilege) — a workflow that needs write scopes requests it explicitly via a `permissions:` block.",
64
+ "enum": ["read", "write"],
65
+ "default": "read"
66
+ },
67
+ "actionsCanApprovePullRequestReviews": {
68
+ "type": "boolean",
69
+ "description": "Whether GitHub Actions workflows are permitted to approve pull requests. The fleet baseline requires this false — an automated actor approving its own (or any) PR is a bypass of human review, not a feature.",
70
+ "default": false
71
+ },
72
+ "_note": {
73
+ "type": "string",
74
+ "description": "Human-readable note for maintainers."
75
+ }
76
+ },
77
+ "_note": "PR templates must stay commit-body-safe: with squashMergeCommitMessage=PR_BODY, the full PR description becomes the literal squash-commit body on main, which release-please and commitlint then parse. A PR template that injects checklist boilerplate, HTML comments, or non-conventional-commit prose into that body will land in the commit history and can break release-please's changelog generation or commitlint's body-line rules. Keep PR templates short, commit-message-safe prose, or move checklists into template sections that authors delete before merge."
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel-platform",
3
- "version": "0.17.2",
3
+ "version": "0.18.0",
4
4
  "description": "Shared CI/deploy workflows, composite toolchain action, npm config package, Renovate preset, and operator runbook templates.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,6 +21,7 @@
21
21
  "./biome.base.json": "./config/biome.base.json",
22
22
  "./knip.base.json": "./config/knip.base.json",
23
23
  "./stryker.base.json": "./config/stryker.base.json",
24
+ "./commitlint.base.mjs": "./config/commitlint.base.mjs",
24
25
  "./dependency-cruiser.base.json": "./config/dependency-cruiser.base.json",
25
26
  "./size-limit.base.json": "./config/size-limit.base.json",
26
27
  "./lighthouse.base.json": "./config/lighthouse.base.json",