CONTENT-PROMOTION-W1-MBTI-RESULTS-ADAPTER-01: activate audited W1 MBTI result promotion - #3498
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b94b2c01a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| DB::table('content_release_manifests')->insert([ | ||
| 'content_pack_release_id' => $package['release_id'], | ||
| 'manifest_hash' => $package['authority_hash'], | ||
| 'schema_version' => 'fermatmind.content_promotion.mbti_result_authority.v2', |
There was a problem hiding this comment.
Keep schema_version within the production column width
The inserted schema identifier is 53 characters, but content_release_manifests.schema_version is still VARCHAR(32) and the production MySQL connections use strict mode. Consequently, every new MBTI result draft-import reaches this insert and fails with a data-too-long error; the SQLite-focused tests do not enforce the column width.
Useful? React with 👍 / 👎.
| 'assets.json', | ||
| 'inventory_reconciliation.json', | ||
| 'entitlement_matrix.json', | ||
| 'pdf_reader_fixture_mapping.json', | ||
| ]; |
There was a problem hiding this comment.
Require completed W9 evidence before enabling publication
The current result package explicitly records independent_w9: pending in package_manifest.json/approval_envelope.json and independent_w9: false in editorial_review.json, but the adapter requires and validates only these four payloads before allowing draft import and activation. Because the production workflow supplies no separate W9 artifact, dispatching it for the checked-in package would publish a cohort that has not passed the repository's mandatory W9 gate; require and validate the exact completed review evidence before returning a promotable package.
AGENTS.md reference: AGENTS.md:L196-L196
Useful? React with 👍 / 👎.
| 'content_hash' => $package['authority_hash'], | ||
| 'pack_version' => self::PACK_VERSION, | ||
| 'manifest_json' => $canonicalAuthority, | ||
| 'storage_path' => 'database/content_pack_releases/'.$package['release_id'], |
There was a problem hiding this comment.
Make the activated release resolvable by the MBTI runtime
This database storage path sends the release through ContentPackV2Resolver::resolveExactDatabaseBackedMbtiInactiveResultPack(), which accepts only the older fixed release/action/hash and also rejects it whenever an active pointer exists. Thus, after publish() activates this newly generated release, active resolution returns null; no MBTI runtime loader was added to consume the new manifest, while liveQa() merely rereads the database row and still reports all 46 targets published. The publication must be wired into the actual runtime resolver and verified through that projection rather than treating pointer existence as live QA.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
|
Addressed the three P1 findings in 2d1539a: production-width schema version, exact-SHA independent W9 PASS gate, and active V2 MBTI runtime authority resolution in live QA. Focused regression coverage is included; the frozen candidate remains fail-closed while its actual W9 is pending. @codex address that feedback |
A. Changed FilesAdded
Modified
Summary
B. Copy-Paste BlocksThe requested fixes are already applied and committed. The exact principal replacement/insertion points are: Production-width manifest schemaReplace the manifest insert’s 'schema_version' => 'mbti_result_promotion.v2',Insertion location: Independent W9 validationInsert the independent-evidence gate immediately after required package-file validation: $this->assertIndependentW9Evidence($manifest, $packageSha256);Insertion location: after the Runtime-backed live QAReplace pointer-only live QA with the following runtime projection comparison: $runtimePayload = $this->runtimeResolver->resolveActiveMbtiResultAuthority();
if ($runtimePayload === null
|| ! hash_equals(
PromotionContextFactory::canonicalJson($payload),
PromotionContextFactory::canonicalJson($runtimePayload),
)) {
throw new DomainException('mbti_result_live_qa_runtime_projection_mismatch');
}Replacement location: C. Minimal Acceptance CommandsTesting and checks run
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47f54cef9b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| $runtimePayload = $this->runtimeResolver->resolveActiveMbtiResultAuthority(); | ||
| if ($runtimePayload === null | ||
| || ! hash_equals( | ||
| PromotionContextFactory::canonicalJson($payload), | ||
| PromotionContextFactory::canonicalJson($runtimePayload), |
There was a problem hiding this comment.
Route the activated authority into MBTI result consumers
When W1/mbti-results is published, this live QA only compares the release row with a new database resolver; repo-wide search shows resolveActiveMbtiResultAuthority() is otherwise unused outside tests, while actual result consumers such as MbtiResultPersonalizationService and ReportGatekeeper continue loading the physical content pack. Fresh evidence after the attempted resolver fix is that no product runtime caller was added, so the workflow can report all 46 rows published while users still receive the old pack content. Wire this authority into the real result/report/share/PDF consumers and exercise those projections during live QA.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cf90b44c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 'package_sha256' => $packageSha256, | ||
| 'authority' => $authority, | ||
| 'authority_hash' => $authorityHash, | ||
| 'release_id' => $this->deterministicUuid('fermatmind:content-promotion:w1-mbti-results:'.$packageSha256), |
There was a problem hiding this comment.
Keep the release identity consistent across control-plane updates
When the same immutable package is promoted again after a new control-plane deployment, sourceCommit changes the stored authority payload and therefore its hash, but this release ID remains derived solely from the package SHA. importDraft() then finds the prior row under the same ID and rejects it as mbti_result_release_identity_collision, preventing a legitimate retry after an executor fix or deployment. Either include the source commit in the release identity or keep deployment-specific metadata outside the package-derived authority payload so the exact package remains idempotently promotable.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
|
Addressed the follow-up runtime P1 and retry P2 in ed1ce05: the active English W1 authority is now consumed by MbtiResultPersonalizationService for private result/report projections, public share stays excluded, and source_commit is no longer part of package-derived release identity. Focused T2 and runtime-freeze validation passed (423 tests / 748 assertions). @codex review |
eee2642 to
0c8f0ce
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c8f0ce862
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ($asset !== null && str_starts_with($field, 'sections.')) { | ||
| $assets[substr($field, strlen('sections.'))] = $asset; |
There was a problem hiding this comment.
Project every promoted candidate asset
For the checked-in 21-candidate package, this condition admits only the 16 authority_field values beginning with sections.; the locked-result commercial_spec.variants[].cta_copy asset and four premium_teaser.* assets are silently discarded, so locked/full result consumers keep the old CTA and teaser content even while publication/live QA reports the entire cohort published. Fresh evidence after the earlier runtime-consumer finding is this explicit filter that leaves five candidate assets without any consumer; wire those authority buckets into offer_set and premium teaser projections and verify them during live QA.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
| 'identity_variant' => trim((string) ($identity['variant_code'] ?? 'identity pattern')), | ||
| 'axis_label' => 'preference axis', 'side_label' => 'the reported side', 'opposite_side_label' => 'the opposite side', 'delta' => 'a measured', | ||
| 'neighbor_type' => 'a nearby pattern', 'adjacent_axis_label' => 'a nearby preference axis', 'adjacent_side_label' => 'the reported side', 'adjacent_opposite_side_label' => 'the opposite side', |
There was a problem hiding this comment.
Render templates from measured personalization
When an activated package renders the close-call, adjacent-type, or identity assets, these hard-coded substitutions discard the actual values already present in axis_vector, close_call_axes, neighbor_type_keys, and the scalar identity field. For example, with a {{delta}}-point margin becomes with a a measured-point margin, and every user sees generic text such as “preference axis” and “a nearby pattern” instead of their measured axis and neighbor type; derive this context from the personalization payload rather than placeholder prose.
Useful? React with 👍 / 👎.
| $previousReleaseId = data_get($snapshot->meta_json, 'activation_before_release_id'); | ||
| if ($previousReleaseId !== null && (! is_string($previousReleaseId) || $previousReleaseId === '')) { | ||
| throw new DomainException('mbti_result_rollback_snapshot_invalid'); | ||
| } | ||
| $this->authority->restoreActivation($previousReleaseId); |
There was a problem hiding this comment.
Compare the active release before rollback
If another existing content-pack publisher activates this pack after this run's publication but before its live-QA rollback, this call unconditionally restores the older snapshot and overwrites the newer activation. The snapshot already records target_release_id, so rollback should lock the activation and fail closed unless its current release still equals that target before restoring the prior release.
AGENTS.md reference: AGENTS.md:L197-L197
Useful? React with 👍 / 👎.
| use App\Services\AI\ControlledNarrativeLayerService; | ||
| use App\Services\Comparative\VersionedComparativeNormingLayerService; | ||
| use App\Services\Content\ContentPacksIndex; | ||
| use App\Services\Content\ContentPackV2Resolver; |
There was a problem hiding this comment.
Declare the runtime files in the train scope
This runtime change is outside the task's own allowed_paths: the added manifest entry permits backend/app/Services/ContentPromotion/** but not backend/app/Services/Mbti/MbtiResultPersonalizationService.php, backend/app/Services/Content/ContentPackV2Resolver.php, or the modified Big Five test, while stop_if_changed_files_outside_scope is explicitly true. Update the declared scope/allowlist or separate these changes before accepting the commit.
AGENTS.md reference: backend/AGENTS.md:L3-L7
Useful? React with 👍 / 👎.
09f9a82 to
e0bcfea
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0bcfeabf1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ($this->normalizeLocale((string) ($personalization['locale'] ?? '')) !== 'en') { | ||
| return $sections; | ||
| } | ||
| $authority = $this->contentPackV2Resolver->resolveActiveMbtiResultAuthority(); |
There was a problem hiding this comment.
Preserve the constructor-free capability preflight
When MbtiResultEnglishRuntimeCapabilityPreflightService::assertRuntimeProjectionSubstitutesTokens() runs, its renderer is intentionally created with ReflectionClass::newInstanceWithoutConstructor(). This newly added resolver dereference therefore accesses an uninitialized readonly property and throws an Error, preventing the controlled read-only MBTI runtime-capability preflight from producing its receipt; construct the renderer with its dependencies or keep this lookup safe for that constructor-free validation path.
Useful? React with 👍 / 👎.
| [ | ||
| 'created_count' => (int) ($receipt['created_count'] ?? 0), | ||
| 'updated_count' => (int) ($receipt['updated_count'] ?? 0), | ||
| 'unchanged_count' => $context->expectedRowCount - $written, |
There was a problem hiding this comment.
Keep W1 comparisons out of the result-adapter change
The declared CONTENT-PROMOTION-W1-MBTI-RESULTS-ADAPTER-01 manifest explicitly says not to change W1/mbti-comparisons, but this hunk changes that adapter's emitted operation-count contract, alongside making those fields required in the shared receipt schema. This is adjacent-lane behavior rather than the scoped result adapter, so revert or split it instead of altering the already executable comparison lane in this commit.
AGENTS.md reference: backend/AGENTS.md:L3-L7
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2fe5605c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| $section['body_md'] = implode("\n\n", $body); | ||
| $section['payload'] = $payload; |
There was a problem hiding this comment.
Replace legacy blocks with the promoted section copy
When a result section has dynamic personalization, the earlier loop has already copied its pre-promotion body_md and dynamic text into payload.blocks; this hunk replaces only the top-level body_md. Any result/report client rendering payload.blocks therefore continues serving legacy pack copy, potentially contradicting the promoted body while _meta.authority_source claims the new authority. Fresh evidence after the earlier runtime-wiring changes is this surviving block projection; rebuild or clear the blocks from the promoted templates and validate them during live QA.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
| $manifestPayload = $this->decodeJsonObject((string) ($manifest->payload_json ?? '')); | ||
| if ($manifestPayload === null || json_encode($payload) !== json_encode($manifestPayload)) { |
There was a problem hiding this comment.
Recompute the promoted authority hash before resolving
If manifest_json and payload_json are changed identically by corruption or a database repair while their stored hashes remain unchanged, this equality check accepts the modified payload because the resolver never recomputes its canonical hash against release.manifest_hash/content_hash or manifest.content_hash. The runtime can consequently serve content that is no longer cryptographically bound to the exact promoted package; verify the canonical payload hash before returning it.
AGENTS.md reference: AGENTS.md:L197-L198
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ef8f4d813
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ($this->normalizeLocale((string) ($personalization['locale'] ?? '')) !== 'en') { | ||
| return $sections; |
There was a problem hiding this comment.
Preserve locale when rebuilding personalization
For an English report whose saved report._meta.personalization is absent, MbtiPublicProjectionService rebuilds personalization with buildForReportPayload(), but that method does not retain the supplied locale. This check therefore normalizes the missing value to zh-CN and skips the activated English authority, leaving older or partial reports on legacy copy even though their personalization rebuild succeeds. Carry the context locale into the returned personalization or pass it separately into this projection step.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
| 'source' => [ | ||
| 'package_id' => (string) ($manifest['package_id'] ?? ''), | ||
| 'package_sha256' => $packageSha256, | ||
| 'manifest_sha256' => hash('sha256', $manifestBytes), |
There was a problem hiding this comment.
Keep mutable manifest metadata out of the authority hash
Fresh evidence after the earlier release-identity fix is that manifest_sha256 still makes the authority hash vary independently of the package SHA: changing or renewing the W9 report_ref changes package_manifest.json, while the package SHA remains the hash of only the listed file chain. A retry then derives the same release ID from the package SHA but a different authority hash and fails with mbti_result_release_identity_collision. Keep approval metadata outside the package-derived authority payload or bind it into the release identity.
AGENTS.md reference: AGENTS.md:L196-L198
Useful? React with 👍 / 👎.
| private function assertSafePayload(mixed $value, ?string $key = null): void | ||
| { | ||
| if (is_string($key) && in_array(strtolower($key), self::FORBIDDEN_PRIVATE_KEYS, true)) { | ||
| throw new DomainException('mbti_result_private_payload_forbidden'); |
There was a problem hiding this comment.
Reject suffixed private identifier keys
For a submitted package asset containing common private fields such as attempt_id, report_id, order_id, or payment_id, this exact-key comparison accepts the payload because only the unsuffixed forms are denied. The accepted asset is then persisted in both authority JSON records despite receipts asserting zero private payload reads; extend the check to the repository's complete private-field set rather than matching only these base names.
AGENTS.md reference: AGENTS.md:L198-L198
Useful? React with 👍 / 👎.
| - backend/app/Services/Content/ContentPackV2Materializer.php | ||
| - backend/app/Services/Content/ContentPackV2Resolver.php | ||
| - backend/app/Services/Mbti/MbtiResultPersonalizationService.php |
There was a problem hiding this comment.
Revert scope expansion in unrelated train cards
This W1 results commit retroactively adds MbtiResultPersonalizationService.php to AUDIT-PRR2-API-14, whose declared scope is only content-pack LKG materialization; it similarly broadens other pre-existing train cards elsewhere in this file. Those changes invent unrelated future scope rather than declaring the current W1 card, so restore the older cards and keep the runtime paths only in CONTENT-PROMOTION-W1-MBTI-RESULTS-ADAPTER-01.
AGENTS.md reference: AGENTS.md:L178-L180
Useful? React with 👍 / 👎.
What changed
Why
W1 MBTI result promotion must be executable only through backend authority with an auditable, exact-SHA lifecycle; it must not write the deployed content-pack tree or read private result/payment data.
Authority/revision model
The adapter derives one deterministic release identity from the submitted package SHA, persists package-derived authority only in existing content_pack_releases and content_release_manifests records, switches only the matching MBTI English activation, and restores only the captured prior activation.
Idempotency and rollback
Identical package/phase replays return exact readback with zero writes. Publication binds the imported release; failed live QA restores only the pre-publication activation snapshot.
Validation
Repository rule impact
Documents the W1 result V2 database-authority boundary, immutable deployed-tree protection, and explicit receipt operation counts.
Deferred items
No content package payload, production CMS promotion, publication, discoverability mutation, deployment, or later-lane adapter was executed.
No production CMS write, publication, or deploy occurred.