fix(project-engine-client, user-manager-client): round-trip created+published market + live-fidelity sweep (#1745)#1746
Conversation
…e mock (#1745) The Project Engine Counterfact mock dropped a just-created market: the create read-view returned an empty settings.ai.language.name, so the consumer's langOf mapped it to null and listMarkets dropped the project, and publish never flipped the stored status. Resolve the create read-view's language.name to the ISO code (via a new shared mock/language-catalog.js id<->name<->iso catalog) and country.name via Intl.DisplayNames, and make publish flip the stored publish_status draft->live and stamp published_at (leaving is_draft as-is, matching live). Also fold in the second-angle live sweep (2026-06-29) fidelity items: - read-view echoes null (not []/'') for an omitted brand_names / location_name - prompt create dedups by text into existing_count; benchmark create 409s on a duplicate brand name / alias / domain - DELETE of a non-existent project 404s (serenity treats 404 as success) - document the no-ownership model, error layering, pagination asymmetry, and the empty brand-topics prompts as intentional mock simplifications All mock-fidelity / doc items; no production-code change. Unit 100% coverage, types + lint clean, e2e (38) green incl. the round-trip. Introduced by: #1745 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…own ws (#1745) Second-angle live sweep (2026-06-29) fidelity items for the User Manager mock, complementary to the project-engine round-trip fixes: - correct the pool-exhaustion 422 to the live string "insufficient available units in subscription" (child + transfer handlers) - GET family on an unknown workspace now 403s, mirroring GET status (was 200 []) - document the no-ownership / presence-only-bearer model, the resources-not-actually -required-on-create divergence, the stuck-not-ready / 422-on-delete zombie window, and the spurious mock-only role field as intentional simplifications All mock-fidelity / doc items; no production-code change. Unit 100% coverage, types + lint clean, e2e (14) green incl. the new family-403 case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MysticatBot
left a comment
There was a problem hiding this comment.
@/tmp/mysticat-workspace-4_vd1e3t/review-body-1746.md
MysticatBot
left a comment
There was a problem hiding this comment.
Hey @rainer-friederich,
Verdict: Request changes - two mock fidelity gaps in the benchmark conflict detection path need attention.
Complexity: HIGH - large diff; API surface.
Changes: raises fidelity of the PE and UM Counterfact mocks so a created+published market round-trips, adds dedup/conflict detection, and documents live-vs-mock divergences (19 files).
Must fix before merge
- [Important] Benchmark intra-batch conflict not checked - conflicting entries within the same POST batch are not detected -
packages/spacecat-shared-project-engine-client/mock/counterfact/routes/v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks.js:24(details inline) - [Important] Benchmark
brand_aliasesconflict path has zero test coverage -packages/spacecat-shared-project-engine-client/test/e2e/project-engine-mock.e2e.js:690(details inline)
Non-blocking (5): minor issues and suggestions
- nit:
nullable()wrapper could be a typed constant instead of a function call -packages/spacecat-shared-project-engine-client/mock/factories.js:356 - suggestion:
identityTokensdoes not guard against non-arraybrand_aliases(spread of a string iterates characters) -packages/spacecat-shared-project-engine-client/mock/counterfact/routes/v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks.js:279 - suggestion: publish e2e test should assert
published_atis a valid ISO 8601 timestamp via round-trip parse -packages/spacecat-shared-project-engine-client/test/e2e/project-engine-mock.e2e.js:561 - suggestion: language catalog test could verify
isouniqueness alongside the existingiduniqueness check -packages/spacecat-shared-project-engine-client/test/mock/language-catalog.test.js:749 - suggestion: add a
CATALOG_CAPTUREDdatestamp constant tolanguage-catalog.jsso future drift from the live Semrush catalog is discoverable -packages/spacecat-shared-project-engine-client/mock/language-catalog.js:1
- benchmark create now detects intra-batch conflicts (fold each entry's identity tokens into the seen-set while iterating), not just collisions with stored rows - identityTokens guards against a non-array brand_aliases (spread of a string would iterate characters into spurious tokens) - e2e: cover the brand_aliases cross-collision branch + an intra-batch duplicate; assert published_at is a valid ISO 8601 timestamp (round-trips through Date) - nullable read-view helper is now a typed constant instead of a function call - language-catalog: export a CATALOG_CAPTURED datestamp so live-catalog drift is discoverable; assert its shape in the catalog test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This PR will trigger a patch release when merged. |
|
Addressed the review in Must fix:
Non-blocking:
|
There was a problem hiding this comment.
Hey @rainer-friederich,
⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.
Verdict: Approve - fix commit cleanly resolves both prior blocking findings; no new issues.
Complexity: HIGH - large diff; API surface.
Changes: raises fidelity of the PE and UM Counterfact mocks so a created+published market round-trips, adds dedup/conflict detection with intra-batch support, and documents live-vs-mock divergences (19 files).
Previously flagged, now resolved
- Benchmark intra-batch conflict not checked - fixed with fold-into-taken pattern in
entries.some()loop brand_aliasesconflict path untested - e2e added for alias cross-collision plusArray.isArrayguard on the spread
Non-blocking (1): minor issues and suggestions
- nit: intra-batch e2e only covers brand_name collision, not domain-only collision within a single batch (the implementation is generic across all token types, but a domain-collision variant would pin that) -
packages/spacecat-shared-project-engine-client/test/e2e/project-engine-mock.e2e.js:712
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 6s | Cost: $3.90 | Commit: dcded4b485639d26c4649af140b8ab3b89acb2b4
If this code review was useful, please react with 👍. Otherwise, react with 👎.
…atBot nit on #1745) Add an e2e that POSTs two benchmarks with distinct brand names but a shared domain in one batch and asserts 409 — pins that the intra-batch conflict check is generic across token types (domain, not just brand_name), per MysticatBot's non-blocking suggestion on the approved review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the non-blocking nit from the approved review in MysticatBot:
Re-requesting so the approval lands on the new HEAD. |
There was a problem hiding this comment.
Hey @rainer-friederich,
⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.
Verdict: Approve - nit addressed cleanly; no new issues.
Complexity: HIGH - large diff; API surface.
Changes: raises fidelity of the PE and UM Counterfact mocks so a created+published market round-trips, adds dedup/conflict detection with intra-batch support, and documents live-vs-mock divergences (19 files).
Previously flagged, now resolved
- Intra-batch e2e now covers domain-only collision within a single batch (distinct names, same domain in one POST)
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 4m 55s | Cost: $1.39 | Commit: 8d582bc841bb8125d22ff6be77e2664a18890c3c
If this code review was useful, please react with 👍. Otherwise, react with 👎.
…t hidden, #1745) The completeness sweep on #1745 flagged that serenity's updateProject omits the required `type`, which 400s against live Semrush. The vendored spec already marks ProjectUpdateRequest.type required and the mock enforces it at Counterfact's request-validation seam, so the mock does NOT hide the bug — but there was no test proving it. Add an e2e asserting a typeless PATCH 400s, so a future overlay/handler change that dropped the requirement would be caught (and the serenity bug surfaces in the IT rather than passing silently). No overlay/spec change needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Added one regression test in
Re-requesting so the approval lands on the new HEAD. |
There was a problem hiding this comment.
Hey @rainer-friederich,
⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.
Verdict: Approve - regression test cleanly pins existing validation behavior; no new issues.
Complexity: HIGH - large diff; API surface.
Changes: raises fidelity of the PE and UM Counterfact mocks so a created+published market round-trips, adds dedup/conflict detection with intra-batch support, and documents live-vs-mock divergences (19 files).
Previously flagged, now resolved
- Intra-batch e2e covers domain-only collision within a single batch (distinct names, same domain in one POST)
- New commit pins that a typeless PATCH returns 400 (Counterfact enforces the vendored spec's required
typefield), confirming the mock surfaces the serenity omit-type bug rather than hiding it
Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 6m 3s | Cost: $1.67 | Commit: b3818f5ad36618fb73cf4411dc29fac8d6406c12
If this code review was useful, please react with 👍. Otherwise, react with 👎.
`model.ProjectUpdateRequest.type` is REQUIRED — live Semrush (and the PE mock now that adobe/spacecat-shared#1746 enforces it) 400 a typeless PATCH. The own-brand alias re-sync (syncBrandAliasesAcrossMarkets, on the brand create/edit fan-out) omitted it, so the project brand_names re-sync silently failed best-effort against live. Send `type: 'ai'` (serenity projects are AI projects — the same literal createProject uses). The transport's updateProject `body` param is loosely typed, which is why tsc never flagged the missing required field; spacecat-shared#1746 commit b3818f5a deliberately pins the mock-side 400 so the bug surfaces. Unit tests updated to assert the type on the PATCH body. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## [@adobe/spacecat-shared-project-engine-client-v1.3.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-project-engine-client-v1.3.1...@adobe/spacecat-shared-project-engine-client-v1.3.2) (2026-06-29) ### Bug Fixes * **project-engine-client, user-manager-client:** round-trip created+published market + live-fidelity sweep ([#1745](#1745)) ([#1746](#1746)) ([d3d823c](d3d823c)), closes [#1742](#1742)
|
🎉 This PR is included in version @adobe/spacecat-shared-project-engine-client-v1.3.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
## [@adobe/spacecat-shared-user-manager-client-v1.3.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-user-manager-client-v1.3.0...@adobe/spacecat-shared-user-manager-client-v1.3.1) (2026-06-29) ### Bug Fixes * **project-engine-client, user-manager-client:** round-trip created+published market + live-fidelity sweep ([#1745](#1745)) ([#1746](#1746)) ([d3d823c](d3d823c)), closes [#1742](#1742)
|
🎉 This PR is included in version @adobe/spacecat-shared-user-manager-client-v1.3.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
…#2721) <!-- mysticat-pr-skill --> ## 1. Abstract Adds the required `type: 'ai'` field to the serenity brand-alias re-sync `updateProject` PATCH body so the request validates against the Semrush Project Engine gateway. ## 2. Reasoning The brand-alias re-sync in `src/support/serenity/brand-aliases.js` called `transport.updateProject(...)` with a body that omitted the `type` field, which is `required` on `ProjectUpdateRequest`. A live sweep (captured 2026-06-29) confirmed that a typeless `PATCH /v1/workspaces/{id}/projects/{pid}` returns `400` (`Key: 'ProjectUpdateRequest.Type' Error:Field validation for 'Type' failed on the 'required' tag`), while the same PATCH with `type: 'ai'` returns `200`. As a result, any brand-alias change that actually drifts a project's `brand_names` would fail in production and silently no-op the project-level alias sync. The gap went uncaught because no IT exercised the alias-resync PATCH path against the PE mock, even though the mock already 400s a typeless PATCH. ## 3. High-level overview of the changes - Before: the `brand_names` re-sync branch sent `{ brand_name_display?, brand_names }` — no `type` — so a drifted update 400'd against the live gateway and the alias sync silently failed. - After: the PATCH body carries `type: 'ai'` (matching the literal used at project create-time in `handlers/markets.js` and `handlers/markets-subworkspace.js`; AIO projects are always type `ai`), so the request validates and the drifted `brand_names` update succeeds. - Behaviour is otherwise unchanged: the PATCH still fires only when the live `brand_names` set has drifted from the desired set; the benchmark `brand_aliases` PUT path is untouched. - The three exact-body assertions in the unit test now pin `type: 'ai'`, so a future regression that drops the field is caught locally. ## 4. Required information - Jira / issue: #2720 - Other: adobe/spacecat-shared#1746 ## 5. Affected / used mysticat-workspace projects - spacecat-shared (contract): the PE Counterfact mock enforces the `type` requirement at the request-validation seam; spacecat-shared PR #1746 pins the typeless-PATCH 400 as an e2e. This change makes serenity conform to that contract. ## 7. Test plan - (a) Local: ran the `brand-aliases` unit suite, which now asserts `type: 'ai'` on all three `updateProject` body expectations; the serenity `// @ts-check` type-check gate also covers the touched file. No live e2e was run this session — the production defect was identified from the 2026-06-29 live Semrush sweep referenced in the issue. - (b) dev / stage / prod: after deploy, trigger a brand-alias change that drifts a serenity-mode brand's `brand_names` (display name or alias edit) and confirm the `PATCH /v1/workspaces/{id}/projects/{pid}` returns `200` rather than `400`, and that the project's `settings.ai.brand_names` reflects the new set. ## 8. Deployment & merge order - adobe/spacecat-shared#1746 (related): pins the mock-level typeless-PATCH 400 guard upstream. Independent of this PR — no hard merge/deploy ordering required; the two together guard the defect at both the mock layer and the serenity call site. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…round-trip unconditionally adobe/spacecat-shared#1746 merged and released the round-trip mock: @adobe/spacecat-shared-project-engine-client 1.3.1 -> 1.3.2 and @adobe/spacecat-shared-user-manager-client 1.3.0 -> 1.3.1. The harness derives the mock image tag from the installed client version, so the serenity IT now boots the published round-trip mocks. The created+published market round-trip is now the pinned contract, so the read-back / prompt-lifecycle block drops its capability probe and runs unconditionally — a regression in the mock or transport fails loudly rather than silently skipping. Full serenity IT: 26 passing (was 22 + 4 pending) against the published 1.3.2 / 1.3.1 images. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1. Abstract
Raises the fidelity of the Project Engine and User Manager Counterfact mocks (
spacecat-shared-project-engine-client,spacecat-shared-user-manager-client) so a created+published market round-trips, plus the complementary live-vs-mock divergences a second sweep surfaced. Mock + documentation only — no production-code change.2. Reasoning
With the publish 406 fixed in 1.3.1 (#1742),
POST markets/activatereach a published201against the PE mock, but a just-created market did not round-trip: it never appeared inGET markets,GET markets/{geo}/{lang}404'd, and a prompt against that slice was skipped. Root cause: the mock's created-project read-view returned an emptysettings.ai.language.name, so the consumer'slangOfresolvednullandlistMarketsdropped the project; andpublishnever flipped the storedpublish_status. A subsequent live-fidelity sweep against the real Semrush gateway (captured in issue 1745) surfaced a set of complementary mock-vs-live divergences worth closing while here. None is a production bug.3. High-level overview of the changes
Project Engine mock:
settings.ai.language.nameto the ISO code the live API returns there (e.g.en) — not the empty string, and not the English display name — via a new sharedmock/language-catalog.js(canonicalid↔ display-name ↔isocatalog). TheGET /v1/languagesroute now reads that catalog through the request context instead of carrying its own duplicate copy.country.nameis resolved from the country code viaIntl.DisplayNames.publishnow moves the stored project to the live state:publish_statusflipsdraft→liveandpublished_atis stamped, whileis_draftis left unchanged (matching live, which keeps ittruepost-publish). A later list/get therefore reports the published status.GET marketsnow lists the market withlanguageCodeand alivestatus,GET markets/{geo}/{lang}resolves, and a prompt attaches to the slice.null(not[]/'') for an omittedbrand_names/location_name; prompt creation dedups by text intoexisting_count; benchmark creation returns a hard409on a duplicate brand name / alias / domain — including two conflicting entries within the same batch; deleting a non-existent project returns404(the consumer treats a delete-404 as success).User Manager mock:
422now returns the live stringinsufficient available units in subscription.GET familyon an unknown workspace now returns403, mirroringGET status(previously200 []).Both packages'
docs/mock-statefulness.mdnow record the intentional simplifications the sweep confirmed (no tenant/ownership model and presence-only bearer; the two-layer live error model and the unguarded 500s the mock does not reproduce; pagination asymmetry across catalogs; emptybrand-topicsprompts; the UM stuck-not ready/ 422-on-delete zombie window and the spurious mock-onlyrolefield).4. Required information
5. Affected / used mysticat-workspace projects
spacecat-api-service— consumed (contract). Its serenity API-level integration suite boots these two mock images; this PR makes the created-market read-back and prompt-lifecycle assertions there pass. No code change required in that repo for the mock behaviour, but it picks up the corrected images once these packages release.6. Additional information outside the code
409; non-existent-project delete404; UM over-allocation422string; UMfamilyon an unknown workspace403) and confirmed the read-back shapes match the live shapes captured in the issue. The live shapes themselves were captured against the real Semrush prod gateway in the sweeps recorded on issue 1745.7. Test plan
mock/(used by local dev and the cross-repo e2e harness) and the typed clientsrc/; there is no service deployment. Downstream verification happens inspacecat-api-service's serenity IT once the corrected mock images are consumed.8. Deployment & merge order
🤖 Generated with Claude Code