Skip to content

feat(data-access): status transition guard + deriveSuggestionStatus (SITES-47091)#1748

Open
alinarublea wants to merge 4 commits into
mainfrom
feature/status-transition-guard-SITES-47091
Open

feat(data-access): status transition guard + deriveSuggestionStatus (SITES-47091)#1748
alinarublea wants to merge 4 commits into
mainfrom
feature/status-transition-guard-SITES-47091

Conversation

@alinarublea

Copy link
Copy Markdown
Contributor

What & why (SITES-47091)

Adds the JS-side enforcement primitives for the Suggestion ↔ FixEntity status lifecycle defined in ADR adobe/mysticat-architecture#174. Today spacecat-shared validates status only as a closed enum — any value from any value, no state machine. This PR adds the transition table + guard + the shared bubble-up function, shipped warn-only by default.

Changes (spacecat-shared-data-access only)

  • Transition tables + predicatesfix-entity.transitions.js (canonical FixEntity table from the ADR), suggestion.transitions.js (intentionally V1-permissive; the warn logs inform tightening before enforce).
  • util/status-transition-guard.jsgetEnforcementMode() reads STATUS_TRANSITION_ENFORCEMENToff | warn | enforce, default warn. guardTransition(): allowed/no-op → pass; warn → log violation (Entity id from -> to) and proceed; enforce → throw ValidationError; off → skip.
  • setStatus override + transitionStatus alias on FixEntity and Suggestion models — a model-defined setStatus blocks the auto-generated setter (base.model.js:128), so this is a single chokepoint capturing every JS writer (api-service single PATCH, bulkUpdateStatus, autofix-worker). Default warn is byte-equivalent to the old setter (isReference was always false for status).
  • deriveSuggestionStatus — the non-CWV 1:1 bubble-up only (DEPLOYED/PUBLISHED→FIXED, FAILED→ERROR, PENDING→IN_PROGRESS, ROLLED_BACK→SKIPPED; severity collapse for >1 fix).
  • Barrel + .d.ts exports.

Rollout / behavior

Default warnzero behavior change on consumer bump. Consumers adopt and observe violation logs, then flip to enforce — tracked in SITES-47286. This guard covers only the two JS writers; mystique (Python direct-DB) bypasses it and needs a separate guard later.

Deferred (agreed, split out)

The CWV multi-issue bubble-up is deferred to SITES-47285: the ADR's per-issue vocabulary (cwvIssueStatus = PATCH_*/GUIDANCE_GENERATED) exists only in mystique, written into the overloaded per-issue status field via direct-DB, while the JS ISSUE_STATUSES enum lists only the Suggestion vocabulary. deriveSuggestionStatus throws if a non-empty issues[] is passed until that's reconciled. FixEntity.REJECTED is likewise omitted (not in the enum yet).

Tests

2441 data-access tests pass; all new files at 100% coverage; lint clean.

Tracking: SITES-47091 · parent SITES-47076 · follow-ups SITES-47285, SITES-47286.

🤖 Generated with Claude Code

…SITES-47091)

Add JS-side enforcement primitives for the Suggestion <-> FixEntity status
lifecycle (ADR adobe/mysticat-architecture#174), shipped warn-only by default:

- FixEntity + Suggestion transition tables + isAllowed* predicates
- guardTransition + getEnforcementMode (env STATUS_TRANSITION_ENFORCEMENT,
  default warn; enforce throws ValidationError; off skips)
- setStatus override + transitionStatus alias on both models, so every JS
  writer is checked at one chokepoint with zero behavior change in warn mode
- deriveSuggestionStatus: non-CWV 1:1 bubble-up only; CWV multi-issue deferred
  to SITES-47076 (per-issue vocabulary lives as PATCH_* on the overloaded
  issue.status field, not yet reconciled in the JS enum)

Default warn mode is byte-equivalent to the prior auto-generated setter, so
consumers can bump and observe violation logs before flipping to enforce.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alinarublea alinarublea requested a review from MysticatBot June 29, 2026 12:14
The integration-test docker-compose pinned mysticat-data-service:v5.24.1,
whose ECR image has been pruned (lifecycle policy) — `manifest unknown` on
pull, failing test:it for any branch freshly cut from main. v5.24.1 also
predates the Preflight migrations the new IT exercises
(20260626153444_drop_async_job_owned_columns_from_preflights.sql first
appears in v5.52.0). Bump to v5.52.0 (latest; image present in ECR; contains
the required migration), matching the "bump on schema-migration change"
convention in the file's comment.

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

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

@MysticatBot MysticatBot 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.

Hey @alinarublea,

Verdict: Approve - well-designed state machine guard with a safe warn-only rollout strategy.
Complexity: HIGH - large diff (977 lines, 16 files).
Changes: Adds status transition tables, a tri-mode enforcement guard, setStatus overrides on FixEntity and Suggestion models, and deriveSuggestionStatus bubble-up function (16 files).

Non-blocking (3): minor issues and suggestions
  • nit: Duplicate status constants across fix-entity.transitions.js, suggestion.transitions.js, and derive-status.js create a sync hazard when the enum grows - packages/spacecat-shared-data-access/src/models/fix-entity/fix-entity.transitions.js:17. The circular-import rationale is sound, but consider extracting to a leaf constants module before flipping to enforce mode.
  • suggestion: transitionStatus(to) is a pure pass-through to setStatus with no distinct behavior or documented guidance on when to prefer it - packages/spacecat-shared-data-access/src/models/fix-entity/fix-entity.model.js:63. Either document the intended semantic split or defer the alias until behavior diverges.
  • suggestion: deriveSuggestionStatus silently returns null when a fix entry is null/undefined or has an unrecognized status. Consider adding a test pinning this edge-case behavior (e.g. deriveSuggestionStatus([null]) returns null) so the contract is explicit - packages/spacecat-shared-data-access/src/models/suggestion/derive-status.js:90.

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 37s | Cost: $3.91 | Commit: 44c7c12e0de4811f4d6828be9e1704812c5620ba
If this code review was useful, please react with 👍. Otherwise, react with 👎.

@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high High complexity PR labels Jun 29, 2026
alinarublea and others added 2 commits June 29, 2026 15:56
v5.52.0 ships SITES-47254 (drop result/error/started_at from preflights),
but the current spacecat-shared Preflight model still defines startedAt/
result/error and the IT fixture still inserts them — so against v5.52.0 the
preflight seed INSERT fails and the Preflight IT sees no rows. v5.51.0 is the
latest data-service release that still has those columns (pre-drop), matching
the model on main. Pin there until the shared model is updated in lockstep
with the SITES-47254 column drop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…LIDATION->IN_PROGRESS

- CLAUDE.md: add "Status Transition Lifecycle" section + STATUS_TRANSITION_ENFORCEMENT
  env var, so consumers/agents discover transitionStatus/setStatus, the predicates
  + tables, and deriveSuggestionStatus (incl. the CWV deferral to SITES-47285).
- suggestion.transitions.js: add PENDING_VALIDATION -> IN_PROGRESS. api-service
  autofixSuggestions legitimately transitions paid-customer suggestions from
  PENDING_VALIDATION to IN_PROGRESS; the V1 table wrongly omitted it (would have
  produced false warnings now and false ValidationErrors under enforce).

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

Labels

ai-reviewed Reviewed by AI complexity:high High complexity PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants