# NetSuite monthly revenue object — creation handoff

## Status / authorization (2026-09-07)

User authorized creating `NetSuite_Revenue_Monthly__c` in CRM. Local metadata preparation is complete; **production creation is NOT complete**. No CRM metadata/data, grants, scheduler, Account relationships or publication controls were changed. User subsequently confirmed proceeding through the formal L2 publication process. Commit/PR review is authorized; deployment remains gated on the reviewed artifact, current target binding and release impact. No deployment was performed.

- Worktree: `/Users/amber/codex/worktrees/nexuscrm-netsuite-monthly`
- Branch: `codex/netsuite-monthly-object`
- Review base: `81db067ae75b93ec613167518da368788d74981b` (rebased from initial `09a1466d69a4a55dc278f8496a5df2ed67720f69`; preserved concurrent voucher-object changes).
- Intended environment: NexusCRM Prod. Exact current serving/release binding and authorized scoped metadata publisher: **UNVERIFIED**.
- Source of truth: `packs/Salesforce-PingCAP-L2/objects/NetSuite_Revenue_Monthly__c.json`, registered in the L2 manifest.
- Generated artifact: `packs/NOT-EDIT-final-merged-packs`; not manually edited and not installed.
- Static build pins: L0 `e013c59433219c8eb26d2bbc84055641b2cab974`, L1 `e0f977d046999eef138a3219a377cdce55720f27`. These reproduce the base artifact's upstream lineage, **not a claim about current Prod versions**.

## Scope and invariants

One custom object, 16 authored fields including `Name`, required Account lookup (`Restrict` deletion), Private sharing, no hierarchy sharing. No Order dependency or fabricated voucher/source-customer identifiers. Prefix `a2Z` replaces the rejected `a1Y`, which belongs to `awsapn__Field_Mapping_Detail__c` in the Salesforce migration snapshot. The contract test checks both L2/final uniqueness and the historical Describe snapshot.

Read-only Prod verification at `2026-09-08 02:30:02 UTC`, TiDB snapshot `468932257761722394`, database `nexuscrm`, `REPEATABLE-READ`: `SELECT COUNT(*) FROM _System_Object WHERE LOWER(TRIM(KeyPrefix))=LOWER('a2Z')` returned **0**, including deleted metadata (no IsDeleted filter). Used the bound export identity with recursively verified SELECT-only grants and rollback; no DB mutation or credential output. This is timestamped prefix-availability evidence, not a reservation, deployment or API acceptance. Recheck at publication.

Monthly grain: month + resolved Account + normalized Product + Category + company + transaction currency. Nine Product options match the current BQ normalization catalog; `Professional and others` is represented only as `Professional Service`. Category options are Cloud/OP. Company/currency are required; signed and zero revenue are allowed, with no USD default or FX calculation. Optional base amount retains source-company base-currency semantics.

`Monthly_Revenue_Key__c` repeats across versions. Only `Snapshot_Row_Key__c` is unique, derived by the publisher from run ID plus monthly key. Required count, constituent payload-set hash and target payload hash support reconciliation. API external-ID behavior is not established merely by declaring `unique`.

Detailed, potentially multiple account_keys remain in BQ; no arbitrary singular source key is stored on an aggregate. Do not add mutable per-row publication flags: consumers must select an independently approved, fully verified run. This change does not implement that control or a publisher. `Source_Line_Count__c` uses the existing numeric metadata type; integrality, first-of-month dates, product/category consistency and exact decimal transport remain writer/runtime acceptance gates.

No new permission grants are included. Private sharing is not an integration-only write guard; effective CRUD/FLS and integration-only authorization remain unverified. The Wetech attribution hold and whole-batch `actuals_freeze_allowed=false` remain binding. Object creation must not initiate revenue import or Actuals publication.

## Local verification

- Final-pack build and `scripts/audit_final_pack_metadata.py`: PASS.
- New monthly contract: 9 tests; existing voucher contract: 9 tests; metadata-audit suite: 18 tests. **36 tests PASS**.
- Existing 86 final-pack object definitions compare exactly with the base after excluding the new monthly object.
- Tracked artifact differences are limited to L2 manifest, final object spec and final build lineage; new source/test/this document are local additions.
- `git diff --check`: PASS.
- Source object SHA256: `fe1d43d3ba0f31c599b49807392b971227d78e1b0779f672a0039352793254a4`.
- Generated final object-spec SHA256: `5cd91b727846e13f7ac4316065ea5e3f5112bc4fea90a7c85eba460d592a20e9`.

These are static source/artifact checks, **not clean-runtime install/verify-pack or serving API acceptance**.

## Next gates / owner: pending

1. Follow the approved formal L2 publication route: reviewed PR/main → successful Formal Staging signed BOM → D3 release Tag → Prod operator (`deployments/online/runbooks/prod.md`). There is no ordinary object-only Prod operator in that contract. Confirm the exact current Prod release/tenant and ensure the release impact remains within the authorized object scope; a broader runtime/metadata upgrade requires separate approval. Fresh GitHub workflow evidence found Prod run `34112943515` successful (`2026-09-07T10:43:38Z`, controller `2a8f58b5b726e280110193a82b067ff0854b3b86`); this supersedes assuming the older cached release is current, but is NOT serving-target/object acceptance. Read live preimage: object absent, prefix unused (including deleted metadata), Account target and pack ownership valid. Do not substitute the read-only DB identity or unrelated credentials.
2. Validate the final pack on a separately bound clean nonproduction runtime through normal install/verify-pack gates. Verify supported field attributes, reference behavior, decimal precision/transport and unique-key metadata through serving Describe. Do not run clean-database reset commands against a shared environment.
3. Publish only the reviewed object delta through the supported L2 metadata channel. This worktree's full pack is NOT authorization for a whole-stack Prod upgrade; if the channel requires a broader release, obtain a separate scope/impact approval first. Do not create an ownerless object through a one-off DB/API bypass and then rely on later pack adoption.
4. Read back all fields, required/reference/uniqueness attributes, indices and ownership; confirm the new object has zero records and old objects remain unchanged. This establishes creation only. Integration CRUD/FLS, upsert and numeric round-trip tests precede a separately authorized writer.

Rollback: no Prod mutation occurred. Local reversal must touch only this worktree's owned delta after preimage comparison. If creation later occurs, retain the empty, nonpublished object while diagnosing; do not assume reverting a pack deletes schema, and do not drop an object/table or its records without a separately verified supported rollback and authorization.
