Skip to content

spec 009: PM integration hardening — make the next provider boring#1143

Merged
zbigniewsobiecki merged 18 commits intodevfrom
spec-009-pm-hardening
Apr 18, 2026
Merged

spec 009: PM integration hardening — make the next provider boring#1143
zbigniewsobiecki merged 18 commits intodevfrom
spec-009-pm-hardening

Conversation

@zbigniewsobiecki
Copy link
Copy Markdown
Member

Summary

Implements spec 009 — PM Integration Hardening across 5 plans. Hardens the PM manifest pattern introduced by spec 006 from a wiring convention into a behavioral contract, eliminating every drift vector that produced the 18-PR tail on Linear's rollout (#1112#1142).

What lands:

Scope guarantee: adding a new PM provider should now be a single PR touching only `src/integrations/pm//` + `web/src/components/projects/pm-providers//` + one import line. Enforced by `tests/unit/integrations/new-provider-surface.test.ts`.

Commits (18): `32987ac9` … `1b5b2cf6` — full list in `git log origin/dev..HEAD`. Pre-push ran 4445 tests passed / 15 skipped locally.

Test plan

  • Unit + integration suites green in CI
  • Lint + typecheck green
  • Pre-merge: `pm-conformance.test.ts` passes for all four provider manifests (Trello, JIRA, Linear, Fake)
  • Pre-merge: `entrypoint-usage.test.ts` + `new-provider-surface.test.ts` guards hold
  • Post-merge smoke: `cascade projects trigger-discover --agent review` still works against a Linear-scoped project

🤖 Generated with Claude Code

zbigniewsobiecki and others added 18 commits April 18, 2026 09:57
Spec 009 turns the PMProviderManifest contract from wiring-only to
behavioral. Decomposes into 5 plans: infra + 3 provider migrations +
cleanup, mirroring spec 006's proven shape.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 1: Branded StateId, LabelId, ContainerId + parsers + InvalidIdError.
Task 2: Extend PMProvider with optional discover? method + associated
        DiscoveryCapability / DiscoveryArgs / DiscoveryResult machinery.
Task 3: Extend PMProviderManifest with optional configSchema,
        discoveryCapabilities, wizardSpec, and lifecycle fields +
        validateManifestAgainstSchema helper.

Plan divergence note: plan 1's claim that switching the PMProvider method
signatures from string to branded types was non-breaking is incorrect —
branded types are NOT assignable from plain strings. Resolved by keeping
PMProvider method signatures as string at the interface level and
deferring per-adapter narrowing to plans 2/3/4, which matches both plan 1's
"dormant" intent and plans 2/3/4's per-adapter adoption ACs.

All new fields are optional → existing manifests compile unchanged. 44
existing pm-conformance tests still pass.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 4: src/integrations/entrypoint.ts centralises every PM/SCM/alerting
registration barrel. Router, worker, CLI bootstrap, and dashboard all
side-effect-import this single file — no per-surface list of imports that
can drift when a new provider is added. Previously dashboard.ts didn't
register any PM providers at all, which is also fixed here.

Task 5: tests/unit/integrations/entrypoint-usage.test.ts grep-asserts the
invariant that every process entry file imports src/integrations/entrypoint.js.
Missing this import was the root cause of #1097, #1118, #1131, #1134 —
the test references those bug numbers in its failure message.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 6: tests/helpers/fakePMProvider.ts ships an in-memory FakePMProvider,
its matching PMProviderManifest (opts into configSchema, every
discoveryCapability, wizardSpec, lifecycle.enabled), and a shared
runLifecycleScenario runner. All seven fake-lifecycle tests pass.

Task 7: tests/unit/integrations/pm-conformance.test.ts extended with 5
new behavioral assertion groups:
  - config round-trip identity (guarded by manifest.configSchema)
  - discovery shape (guarded by manifest.discoveryCapabilities)
  - full lifecycle scenario (guarded by manifest.lifecycle.enabled)
  - trigger self-hook filter (guarded by manifest.isSelfAuthoredHook)
  - webhook verify accept/reject (runs against the fake's HMAC-SHA256)

Legacy providers skip the new groups until they opt in during plans
2/3/4. Harness now runs 80 tests (59 pass, 21 skip — the skips are
legacy providers pending migration, as designed).

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 8: tests/unit/integrations/auth-header-provenance.test.ts greps the
src tree for `Bearer ${...}` / bare string-concat auth-header patterns
outside src/integrations/pm/_shared/auth-headers.ts. Post-#1119, the PM
provider code is clean; 4 non-PM files (Sentry, GitHub SCM, OpenRouter
LLM) are explicitly accept-listed with reasons — all out of spec 009
scope.

Task 9: Biome can't natively express the required string-pattern rule,
so lefthook.yml runs the provenance test at pre-commit (~250ms) —
failures surface at commit time, not just test time. Equivalent to a
custom Biome rule; matches plan 009/1 task 9's fallback guidance.

Also tightened the expanded conformance harness + fake-lifecycle test
to eliminate Biome complexity/non-null-assertion warnings.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 10: src/api/routers/pm-discovery.ts now exposes a generic
`discover({ providerId, capability, args, credentials? })` mutation that
dispatches through the registry. Manifest must declare both
`discoveryCapabilities` and `createDiscoveryProvider`. Returns NOT_FOUND
for unknown providers, NOT_IMPLEMENTED for undeclared capabilities or
missing factories. 7 tests pass.

Task 11: web/src/components/projects/pm-providers/generator.tsx ships
`renderStandardStep(step, ctx)` as dormant scaffolding — returns a
typed placeholder for every StandardStepKind + custom steps. Plans 2/3/4
swap each placeholder for the shared component. Unknown kinds log a
warn-once and render a placeholder instead of crashing.

Task 12: tests/README.md and src/integrations/README.md document the
new fake provider fixture, the lifecycle scenario runner, the behavioral
contract fields on PMProviderManifest, the auth-header provenance test,
and the single-entrypoint invariant.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Ships branded ID types, PMProviderManifest behavioral contract fields,
single registration entrypoint, FakePMProvider fixture + lifecycle runner,
5 new behavioral conformance groups, auth-header provenance enforcement,
generic pm.discover tRPC endpoint, and wizard step generator scaffolding.

All primitives dormant — no provider migrated yet. Plans 2/3/4 flip each
real provider on; plan 5 cleans up legacy surfaces.

415 test files / 7963 tests pass / 21 intentional skips. Lint, typecheck,
and build all green.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Trello opts into configSchema, discoveryCapabilities, wizardSpec,
lifecycle, createDiscoveryProvider, and branded-ID narrowing on
adapter methods. Inline trello schema in src/config/schema.ts marked
@deprecated; plan 5 deletes it.

420 test files, 7988 pass, 19 skip. Lint + typecheck + build green.
Plan divergence notes in the .done file document 5 resolved issues.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
@zbigniewsobiecki zbigniewsobiecki merged commit 754c6b5 into dev Apr 18, 2026
8 checks passed
@zbigniewsobiecki zbigniewsobiecki deleted the spec-009-pm-hardening branch April 18, 2026 12:08
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 18, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant