@happyvertical/smrt-fields 0.42.5 → 0.42.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["policyRowId","claimed"],"sources":["../src/__smrt-register__.ts","../src/deterministic-id.ts","../src/models/FieldPolicySuggestion.ts","../src/collections/FieldPolicySuggestionCollection.ts","../src/models/FieldUsageCounter.ts","../src/models/FieldUsageReportReceipt.ts","../src/collections/FieldUsageCounterCollection.ts","../src/field-policy-resolver.ts","../src/settings-catalog.ts","../src/usage-learning.ts","../src/users-module.ts","../src/usage-schedules.ts","../src/index.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Deterministic row ids for this package's idempotent writes (the\n * `TenantUsageMetric.recordUsage` precedent).\n *\n * A leaf module (no package-internal imports) so both consumers — usage counter\n * buckets and the global learning schedules — share one implementation. The\n * output is formatted as a v5-shaped UUID so id columns stay native UUID on\n * PostgreSQL/DuckDB.\n */\n\n/**\n * SHA-256 over the namespaced parts, formatted as a v5-style UUID.\n *\n * The same parts always produce the same id, which is what turns\n * \"check then create\" into a race-free write: concurrent creators converge on\n * one primary key instead of inserting near-duplicate rows.\n */\nexport async function deterministicFieldsUuid(\n parts: readonly string[],\n): Promise<string> {\n const bytes = new TextEncoder().encode(JSON.stringify(parts));\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));\n const uuid = digest.slice(0, 16);\n uuid[6] = (uuid[6] & 0x0f) | 0x50;\n uuid[8] = (uuid[8] & 0x3f) | 0x80;\n const hex = Array.from(uuid, (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20),\n ].join('-');\n}\n","import {\n crossPackageRef,\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport {\n assertDefaultValueMatchesFieldType,\n getFieldReadPermission,\n getObjectFieldMap,\n isSensitiveField,\n isTransientField,\n} from '../field-definitions.js';\nimport {\n FIELD_POLICY_SUGGESTION_KINDS,\n FIELD_POLICY_SUGGESTION_STATUSES,\n type FieldPolicySuggestionData,\n type FieldPolicySuggestionKind,\n type FieldPolicySuggestionStatus,\n} from '../types.js';\n\n/**\n * `activeKey` sentinel for the single ACTIVE (pending) suggestion per\n * `(objectRef, fieldName, tenantId, kind)`. Settled rows key themselves by id,\n * so history never competes for the slot.\n */\nexport const ACTIVE_SUGGESTION_KEY = 'active';\n\nexport interface FieldPolicySuggestionOptions extends SmrtObjectOptions {\n objectRef?: string;\n fieldName?: string;\n tenantId?: string;\n kind?: FieldPolicySuggestionKind;\n proposedValue?: string | null;\n evidence?: string;\n status?: FieldPolicySuggestionStatus;\n cooldownUntil?: Date | null;\n decidedBy?: string | null;\n decidedAt?: Date | null;\n}\n\n/**\n * A pending, human-reviewable field-policy improvement proposed from real\n * usage (epic #2045, issue #2051): promote a field to the `basic` tier, or\n * seed an org default with the dominant observed value.\n *\n * Suggestion-first by design — a row DOES NOTHING until a\n * `fields.policy.manage` holder accepts it, and acceptance writes the\n * org-scope {@link ../models/FieldPolicy.FieldPolicy} row through NORMAL\n * validation (registry check, type check, security rail, required-field\n * invariant, ownership + permission split). Dismissing sets a cool-down that\n * suppresses regeneration of the same suggestion.\n *\n * **One ACTIVE suggestion per identity, structurally** (not merely by a\n * check-then-insert): {@link activeKey} is a computed column holding the\n * sentinel {@link ACTIVE_SUGGESTION_KEY} while the row is `pending` and the\n * row's own id once it settles, and it participates in `conflictColumns`. The\n * unique index therefore admits at most ONE pending row per\n * `(objectRef, fieldName, tenantId, kind)` while every settled row keys\n * itself — so two overlapping generation runs (e.g. a global and a\n * tenant-specific schedule) UPSERT onto the same row instead of duplicating,\n * with no transaction spanning their reads. It is the `FieldPolicy.scopeKey`\n * trick applied to a lifecycle slot. On settle the column flips to the row's\n * id, which frees the slot for a post-cool-down regeneration while keeping the\n * dismissed/accepted history (and its id) intact — core conflicts a persisted\n * row on its primary key (#1472), so the flip is a plain UPDATE.\n *\n * #1885 seam: this substrate is fully independent of personas'\n * `DirectiveProposal` review queue (no shared producer discriminator —\n * deliberately out of scope). Tenant learning agents MAY create\n * FieldPolicySuggestion rows through this model's normal validation; the\n * reviewed `fields.policy.manage` acceptance gate is unchanged by who\n * proposed.\n */\n// Generated surfaces are CLOSED: reads would enumerate every tenant's\n// suggestion queue (the model is not class-level tenant-scoped, see below),\n// and generated writes would let callers forge evidence or flip status\n// without the gate. All access goes through the collection's scoped actions\n// (`pendingSuggestions`, `acceptSuggestion`, `dismissSuggestion`) or trusted\n// server-side code.\n@smrt({\n tableName: '_smrt_field_policy_suggestions',\n conflictColumns: [\n 'object_ref',\n 'field_name',\n 'tenant_id',\n 'kind',\n 'active_key',\n ],\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldPolicySuggestion extends SmrtObject {\n /** Qualified class name of the target object (`@package/name:ClassName`). */\n @field({ required: true })\n objectRef: string = '';\n\n /** Field name on the target object (validated against the registry). */\n @field({ required: true })\n fieldName: string = '';\n\n /** Owning tenant (required — suggestions always target one org). */\n @tenantId()\n tenantId?: string;\n\n /** What the suggestion proposes ('promote' | 'default'). */\n @field({ required: true })\n kind: FieldPolicySuggestionKind = 'promote';\n\n /**\n * JSON-encoded proposed default (`kind: 'default'` only) — the exact\n * encoding `FieldPolicy.defaultValue` stores, so acceptance passes it\n * through unchanged. NULL for `promote`.\n */\n @field({ type: 'text', nullable: true })\n proposedValue: string | null = null;\n\n /**\n * Human-readable evidence as a JSON string: a `summary` sentence plus the\n * structured window/threshold numbers behind it (see\n * `buildFieldUsageEvidence`).\n */\n @field({ type: 'text' })\n evidence: string = '{}';\n\n /** Lifecycle status ('pending' | 'accepted' | 'dismissed'). */\n @field({ required: true })\n status: FieldPolicySuggestionStatus = 'pending';\n\n /**\n * Computed lifecycle-slot key, set in `save()`: {@link ACTIVE_SUGGESTION_KEY}\n * while `pending`, else the row's own id. It exists ONLY to make the\n * `conflictColumns` unique index express \"at most one ACTIVE suggestion per\n * identity, unlimited settled history\" (the `FieldPolicy.scopeKey`\n * precedent) — never read it for logic; `status` owns that.\n */\n @field({ type: 'text', required: true })\n activeKey: string = ACTIVE_SUGGESTION_KEY;\n\n /**\n * Until this instant, a dismissed suggestion suppresses regeneration of the\n * same `(objectRef, fieldName, tenantId, kind)` suggestion. NULL until\n * dismissed.\n */\n @field({ type: 'datetime', nullable: true })\n cooldownUntil: Date | null = null;\n\n /** Who accepted/dismissed (audit attribution, #2050); not validated. */\n @crossPackageRef('@happyvertical/smrt-users:User', { nullable: true })\n decidedBy: string | null = null;\n\n /** When the suggestion was accepted/dismissed. */\n @field({ type: 'datetime', nullable: true })\n decidedAt: Date | null = null;\n\n constructor(options: FieldPolicySuggestionOptions = {}) {\n super(options);\n if (options.objectRef !== undefined) this.objectRef = options.objectRef;\n if (options.fieldName !== undefined) this.fieldName = options.fieldName;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.kind !== undefined) this.kind = options.kind;\n if (options.proposedValue !== undefined) {\n this.proposedValue = options.proposedValue;\n }\n if (options.evidence !== undefined) this.evidence = options.evidence;\n if (options.status !== undefined) this.status = options.status;\n if (options.cooldownUntil !== undefined) {\n this.cooldownUntil = options.cooldownUntil;\n }\n if (options.decidedBy !== undefined) this.decidedBy = options.decidedBy;\n if (options.decidedAt !== undefined) this.decidedAt = options.decidedAt;\n }\n\n /** Parse the stored evidence object (guarded; junk parses as empty). */\n getEvidence(): Record<string, unknown> {\n try {\n const parsed = JSON.parse(this.evidence);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize an evidence object into the stored JSON string. */\n setEvidence(evidence: Record<string, unknown>): void {\n this.evidence = JSON.stringify(evidence);\n }\n\n /** Parse the proposed value; `undefined` when none is stored. */\n getProposedValue(): unknown {\n if (this.proposedValue === null || this.proposedValue === undefined) {\n return undefined;\n }\n try {\n return JSON.parse(this.proposedValue);\n } catch {\n return undefined;\n }\n }\n\n /** Serialized row shape for the collection actions. */\n toSuggestionData(): FieldPolicySuggestionData {\n return {\n id: String(this.id),\n objectRef: this.objectRef,\n fieldName: this.fieldName,\n tenantId: String(this.tenantId ?? ''),\n kind: this.kind,\n proposedValue: this.proposedValue ?? null,\n evidence: this.getEvidence(),\n status: this.status,\n cooldownUntil: toIsoOrNull(this.cooldownUntil),\n decidedBy: this.decidedBy ?? null,\n decidedAt: toIsoOrNull(this.decidedAt),\n };\n }\n\n override async save(): Promise<this> {\n await this.assertRowOwnedByAmbientContext('save');\n await this.validateFieldPolicySuggestion();\n this.applyActiveKey();\n return super.save();\n }\n\n /**\n * Recompute the lifecycle-slot key: the shared sentinel while pending (so\n * the unique index admits exactly one), the row's own id once settled (so\n * history never competes for the slot and the freed slot allows a\n * post-cool-down regeneration). A settled row that has not been persisted\n * yet is assigned its id here — the key must be unique from the first write.\n */\n private applyActiveKey(): void {\n if (this.status === 'pending') {\n this.activeKey = ACTIVE_SUGGESTION_KEY;\n return;\n }\n if (!this.id) {\n this.id = crypto.randomUUID();\n }\n this.activeKey = String(this.id);\n }\n\n override async delete(): Promise<void> {\n await this.assertRowOwnedByAmbientContext('delete');\n await super.delete();\n }\n\n /**\n * The \"normal validation\" the #1885 seam promises producers: registry-known\n * field, policy-addressable, never sensitive/read-permission-gated/transient\n * (those fields are count-only in usage data and get no suggestions), valid\n * kind/status, and for `default` suggestions a JSON proposed value that\n * type-checks against the manifest field type.\n */\n private async validateFieldPolicySuggestion(): Promise<void> {\n if (!this.objectRef || this.objectRef.trim() === '') {\n throw new Error('FieldPolicySuggestion.objectRef is required');\n }\n if (!this.fieldName || this.fieldName.trim() === '') {\n throw new Error('FieldPolicySuggestion.fieldName is required');\n }\n if (!this.tenantId) {\n throw new Error('FieldPolicySuggestion.tenantId is required');\n }\n if (!FIELD_POLICY_SUGGESTION_KINDS.includes(this.kind)) {\n throw new Error(\n `FieldPolicySuggestion.kind must be one of ` +\n `${FIELD_POLICY_SUGGESTION_KINDS.join(', ')}; got \"${this.kind}\"`,\n );\n }\n if (!FIELD_POLICY_SUGGESTION_STATUSES.includes(this.status)) {\n throw new Error(\n `FieldPolicySuggestion.status must be one of ` +\n `${FIELD_POLICY_SUGGESTION_STATUSES.join(', ')}; got \"${this.status}\"`,\n );\n }\n\n const fields = await getObjectFieldMap(this.objectRef);\n const fieldDef = fields.get(this.fieldName);\n if (!fieldDef) {\n throw new Error(\n `Unknown field \"${this.fieldName}\" on \"${this.objectRef}\"`,\n );\n }\n if (\n fieldDef._meta?.__smrtSystemField === true ||\n fieldDef.type === 'oneToMany' ||\n fieldDef.type === 'manyToMany' ||\n fieldDef.type === 'meta'\n ) {\n throw new Error(\n `Field \"${this.fieldName}\" on \"${this.objectRef}\" is not ` +\n `policy-addressable, so it cannot carry a suggestion`,\n );\n }\n if (\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined ||\n isTransientField(fieldDef)\n ) {\n throw new Error(\n `Field \"${this.fieldName}\" on \"${this.objectRef}\" is sensitive, ` +\n `read-permission-gated, or transient; usage data for it is ` +\n `count-only and it cannot carry a suggestion`,\n );\n }\n\n if (this.kind === 'default') {\n if (this.proposedValue === null) {\n throw new Error(\n \"FieldPolicySuggestion of kind 'default' requires a proposedValue\",\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(this.proposedValue);\n } catch (error) {\n throw new Error(\n `FieldPolicySuggestion.proposedValue is not valid JSON: ` +\n `${error instanceof Error ? error.message : String(error)}`,\n );\n }\n assertDefaultValueMatchesFieldType(\n this.objectRef,\n this.fieldName,\n fieldDef,\n parsed,\n );\n } else if (this.proposedValue !== null) {\n throw new Error(\n \"FieldPolicySuggestion of kind 'promote' must not carry a proposedValue\",\n );\n }\n }\n\n /**\n * Tenant write boundary (the FieldPolicy posture): inside a non-bypass\n * tenant context a caller may only touch its own tenant's suggestions —\n * checked against BOTH the in-memory scope and, for persisted rows, the\n * PERSISTED tenant (a foreign row cannot be re-scoped into the caller's\n * tenant). Trusted execution (no context / bypass) is exempt — that is what\n * lets the scheduled generation job and platform flows operate.\n */\n private async assertRowOwnedByAmbientContext(\n operation: 'save' | 'delete',\n ): Promise<void> {\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return;\n }\n if (this.tenantId !== context.tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldPolicySuggestion.${operation}: ` +\n `context tenant is '${context.tenantId}' but the row belongs to ` +\n `'${this.tenantId}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: this.tenantId ?? undefined,\n },\n );\n }\n if (this.id) {\n const persisted = await this.db.get(this.tableName, { id: this.id });\n if (persisted) {\n const row = persisted as Record<string, unknown>;\n const persistedTenant =\n row.tenantId ?? row.tenant_id ?? this.tenantId ?? null;\n if (\n persistedTenant !== null &&\n String(persistedTenant) !== context.tenantId\n ) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldPolicySuggestion.` +\n `${operation}: the persisted row belongs to ` +\n `'${String(persistedTenant)}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: String(persistedTenant),\n },\n );\n }\n }\n }\n }\n}\n\nfunction toIsoOrNull(value: Date | string | null | undefined): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;\n}\n","import { SmrtCollection, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { assertOperationPermission } from '@happyvertical/smrt-users';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { invalidateFieldPolicyCache } from '../cache.js';\nimport { deterministicFieldsUuid } from '../deterministic-id.js';\nimport {\n assertDefaultValueMatchesFieldType,\n getFieldReadPermission,\n getObjectFieldMap,\n isPolicyAddressableField,\n isSensitiveField,\n isTransientField,\n} from '../field-definitions.js';\nimport {\n ACTIVE_SUGGESTION_KEY,\n FieldPolicySuggestion,\n} from '../models/FieldPolicySuggestion.js';\nimport { MANAGE_FIELD_POLICY_PERMISSION } from '../permissions.js';\nimport type {\n AcceptFieldPolicySuggestionResult,\n DismissFieldPolicySuggestionResult,\n PendingFieldPolicySuggestionsResult,\n} from '../types.js';\n\n/**\n * Raised when a pending→settled transition loses its race: another caller\n * (accept or dismiss) already settled the suggestion — whether the loss is\n * detected by the compare-and-set or by the load that preceded it, so\n * overlapping decisions get ONE error type regardless of timing.\n *\n * Carries BOTH `httpStatus` and `status` = 409. `httpStatus` is the property\n * core's generated REST error mapping honors (an integer OWN property, checked\n * with `Object.hasOwn`); `status` mirrors the users-style shape this package\n * already uses on `FieldPolicyPermissionError`. NOTE: that mapping arrived with\n * #2049 and is NOT in this branch's core yet, so until #2049 lands ahead of\n * this work the generated routes still surface these as 500s — the property is\n * set now so the 409 becomes real the moment it does.\n */\nexport class FieldPolicySuggestionConflictError extends Error {\n /** Core's generated-REST status contract (own integer property). */\n readonly httpStatus = 409;\n /** The users-style shape mirrored by this package's authorization errors. */\n readonly status = 409;\n\n constructor(operation: string, suggestionId: string) {\n super(\n `${operation}: suggestion \"${suggestionId}\" was already decided by ` +\n `another request (it is no longer pending)`,\n );\n this.name = 'FieldPolicySuggestionConflictError';\n }\n}\n\n/** Expected generated-route denial when the suggestion queue lacks a tenant. */\nclass FieldPolicySuggestionRequestContextError extends TenantIsolationError {\n readonly httpStatus = 403;\n readonly status = 403;\n\n constructor(message: string, details?: { tenantId?: string }) {\n super(message, details);\n this.name = 'FieldPolicySuggestionRequestContextError';\n }\n}\n\n/** Transaction handle shape (mirrors `FieldPolicy`'s identity-change path). */\ntype SuggestionTransactionHandle = DatabaseInterface & {\n commit: () => Promise<void>;\n rollback: () => Promise<void>;\n};\n\n/** Options binding a collection to an open transaction (the sales precedent). */\nfunction transactionBoundOptions(db: DatabaseInterface): {\n db: DatabaseInterface;\n _reuseInitializedDb: boolean;\n _deferRuntimeInitialization: boolean;\n} {\n return {\n db,\n // The transaction database is the SAME initialized database on a pinned\n // connection — skip system-table bootstrap and runtime service setup.\n _reuseInitializedDb: true,\n _deferRuntimeInitialization: true,\n };\n}\n\n/**\n * Compare-and-set the pending→settled transition: the UPDATE only applies\n * `WHERE status = 'pending'`, so exactly one of a racing accept/dismiss pair\n * can win. Returns whether this caller claimed it.\n *\n * `RETURNING id` (not a row count) is the reliable \"did it apply\" signal — the\n * DuckDB/JSON adapters report an UPDATE that matched nothing as `rowCount: 1`\n * (the jobs `writeOwnedJob` precedent).\n *\n * Deliberately raw SQL rather than a model save: a conditional transition is\n * not expressible through `save()`, and routing it through the model would\n * re-run PROPOSAL validation — which is exactly what wedges a stale suggestion\n * (its field may since have been removed or become gated). Ownership and the\n * manage permission are asserted by the caller before this runs.\n */\nasync function claimSuggestionTransition(\n db: DatabaseInterface,\n suggestionId: string,\n patch: {\n status: 'accepted' | 'dismissed';\n activeKey: string;\n decidedAt: Date;\n decidedBy: string | null;\n cooldownUntil: Date | null;\n },\n): Promise<boolean> {\n const result = await db.query(\n `UPDATE _smrt_field_policy_suggestions\n SET status = ?,\n active_key = ?,\n decided_at = ?,\n decided_by = ?,\n cooldown_until = ?\n WHERE id = ? AND status = 'pending'\n RETURNING id`,\n patch.status,\n patch.activeKey,\n patch.decidedAt.toISOString(),\n patch.decidedBy,\n patch.cooldownUntil ? patch.cooldownUntil.toISOString() : null,\n suggestionId,\n );\n return (result?.rows?.length ?? 0) > 0;\n}\n\n/**\n * Compensating revert for drivers without transactions: put a claimed row back\n * to `pending` after a refused policy write, so the suggestion is never left\n * settled without its policy. The active slot was held across the whole\n * decision, so this restores state the caller still owns.\n */\nasync function revertSuggestionToPending(\n db: DatabaseInterface,\n suggestionId: string,\n): Promise<void> {\n await db.query(\n `UPDATE _smrt_field_policy_suggestions\n SET status = 'pending',\n active_key = ?,\n decided_at = NULL,\n decided_by = NULL,\n cooldown_until = NULL\n WHERE id = ?`,\n ACTIVE_SUGGESTION_KEY,\n suggestionId,\n );\n}\n\n/**\n * Release the active slot after an accepted decision is fully durable: the row\n * keys itself by its own id, so a post-cool-down regeneration may take the\n * shared slot again while this settled row remains addressable.\n */\nasync function settleSuggestionActiveKey(\n db: DatabaseInterface,\n suggestionId: string,\n): Promise<void> {\n await db.query(\n `UPDATE _smrt_field_policy_suggestions\n SET active_key = ?\n WHERE id = ?`,\n suggestionId,\n suggestionId,\n );\n}\n\n/**\n * Raised when a non-transactional accept could neither complete its policy\n * write NOR undo its claim. Both causes are carried: the row may be sitting\n * `accepted` without a policy, which an operator must reconcile — silently\n * reporting only the policy error would imply the suggestion is still pending.\n */\nexport class FieldPolicySuggestionCompensationError extends Error {\n readonly httpStatus = 500;\n readonly status = 500;\n readonly revertCause: unknown;\n\n constructor(suggestionId: string, cause: unknown, revertCause: unknown) {\n super(\n `acceptSuggestion: the policy write for suggestion \"${suggestionId}\" ` +\n `failed AND reverting it to pending also failed; the suggestion may ` +\n `be left accepted without its policy row and needs reconciliation ` +\n `(policy error: ${cause instanceof Error ? cause.message : String(cause)}; ` +\n `revert error: ${\n revertCause instanceof Error\n ? revertCause.message\n : String(revertCause)\n })`,\n { cause },\n );\n this.name = 'FieldPolicySuggestionCompensationError';\n this.revertCause = revertCause;\n }\n}\n\n/**\n * Stable id for the org-scope policy row an acceptance creates, derived from\n * the policy natural key.\n *\n * With a random id, two concurrent acceptances for the same field would each\n * mint a row and the natural-key upsert would resolve them by REPLACING one\n * (dropping its column and dangling its id). A deterministic id turns that into\n * a primary-key collision the loser can detect and fall back from.\n */\nexport function fieldPolicyRowId(\n tenantId: string,\n objectRef: string,\n fieldName: string,\n): Promise<string> {\n return deterministicFieldsUuid([\n 'field-policy-row',\n 'tenant',\n tenantId,\n objectRef,\n fieldName,\n ]);\n}\n\n/**\n * Atomically set ONE column on the tenant-scope policy row for a field, keyed\n * by the policy natural key. Returns the surviving row id, or `null` when no\n * row exists yet.\n *\n * A single statement: concurrent acceptances of a `promote` and a `default`\n * suggestion for the same field touch disjoint columns and cannot drop each\n * other's write. `RETURNING id` (never a row count) is the reliable applied\n * signal — some adapters report a no-match UPDATE as `rowCount: 1`.\n */\nasync function updatePolicyColumn(\n db: DatabaseInterface,\n target: {\n objectRef: string;\n fieldName: string;\n tenantId: string;\n column: 'visibility' | 'default_value';\n value: string | null;\n decidedBy: string | null;\n },\n): Promise<string | null> {\n const result = await db.query(\n `UPDATE _smrt_field_policies\n SET ${target.column} = ?,\n updated_by = ?,\n updated_at = ?\n WHERE object_ref = ?\n AND field_name = ?\n AND scope_type = 'tenant'\n AND tenant_id = ?\n RETURNING id`,\n target.value,\n target.decidedBy,\n new Date().toISOString(),\n target.objectRef,\n target.fieldName,\n target.tenantId,\n );\n const rows = result?.rows ?? [];\n const id = rows[0]?.id;\n return id === undefined || id === null ? null : String(id);\n}\n\n/**\n * Re-apply the stored-default security rail at ACCEPTANCE time, through the\n * same shared helpers `FieldPolicy` and `FieldPolicySuggestion` validate with\n * (not a copy of the rules).\n *\n * A suggestion is validated when queued, but a field can turn sensitive,\n * `readPermission`-gated, transient, or change type before anyone accepts it —\n * and the atomic column update deliberately bypasses the model, so the rail is\n * asserted here instead of being silently skipped.\n */\nasync function assertAcceptedDefaultStillAllowed(\n suggestion: FieldPolicySuggestion,\n): Promise<void> {\n const fieldDef = await assertAcceptedPolicyTargetStillAllowed(suggestion);\n if (suggestion.proposedValue === null) {\n throw new Error(\n \"FieldPolicySuggestion of kind 'default' requires a proposedValue\",\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(suggestion.proposedValue);\n } catch (error) {\n throw new Error(\n `FieldPolicySuggestion.proposedValue is not valid JSON: ` +\n `${error instanceof Error ? error.message : String(error)}`,\n );\n }\n assertDefaultValueMatchesFieldType(\n suggestion.objectRef,\n suggestion.fieldName,\n fieldDef,\n parsed,\n );\n}\n\n/** Re-check every policy-addressability rail before an atomic acceptance write. */\nasync function assertAcceptedPolicyTargetStillAllowed(\n suggestion: FieldPolicySuggestion,\n) {\n const fields = await getObjectFieldMap(suggestion.objectRef);\n const fieldDef = fields.get(suggestion.fieldName);\n if (!fieldDef || !isPolicyAddressableField(fieldDef)) {\n throw new Error(\n `Field \"${suggestion.fieldName}\" on \"${suggestion.objectRef}\" ` +\n `is not policy-addressable`,\n );\n }\n if (\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined ||\n isTransientField(fieldDef)\n ) {\n throw new Error(\n `Cannot apply a policy for \"${suggestion.objectRef}.` +\n `${suggestion.fieldName}\": the field is sensitive, ` +\n `read-permission-gated, or transient`,\n );\n }\n return fieldDef;\n}\n\n/** Default cool-down a dismissal applies (30 days). */\nexport const DEFAULT_SUGGESTION_COOL_DOWN_MS = 30 * 24 * 60 * 60 * 1000;\n\n/** Bounds for a caller-supplied dismissal cool-down. */\nexport const MIN_SUGGESTION_COOL_DOWN_MS = 60 * 60 * 1000; // 1 hour\nexport const MAX_SUGGESTION_COOL_DOWN_MS = 365 * 24 * 60 * 60 * 1000; // 1 year\n\n/** Upper bound on objectRefs accepted by the pending filter. */\nconst MAX_PENDING_OBJECT_REFS = 100;\n\n/**\n * Collection surface for {@link FieldPolicySuggestion} plus the three\n * manage-gated actions the gear badge / control-panel queue consume (#2051;\n * UI integration is #2050-follow-up territory — this is the minimal seam).\n *\n * All three actions are custom collection-scoped routes (the resolveBatch\n * mechanism — single-segment paths, both transports dispatch). Identity is\n * ambient-context-only and every action requires `fields.policy.manage` (or\n * super-admin bypass) within an ambient tenant: pending suggestions describe\n * org-wide usage, and accept/dismiss are org policy decisions.\n */\n@smrt({\n // Mirror the item's active-slot natural key. The collection decorator emits\n // another schema for the same table; omitting this would reintroduce the\n // default `(slug, context)` unique index in generated migrations.\n conflictColumns: [\n 'object_ref',\n 'field_name',\n 'tenant_id',\n 'kind',\n 'active_key',\n ],\n api: {\n include: ['pendingSuggestions', 'acceptSuggestion', 'dismissSuggestion'],\n // Queue reads and decisions are scoped to the authenticated principal's\n // tenant and require fields.policy.manage; request payloads never select\n // that identity.\n principalContext: true,\n routes: {\n pendingSuggestions: {\n scope: 'collection',\n method: 'POST',\n path: 'pending',\n },\n acceptSuggestion: {\n scope: 'collection',\n method: 'POST',\n path: 'accept',\n },\n dismissSuggestion: {\n scope: 'collection',\n method: 'POST',\n path: 'dismiss',\n },\n },\n },\n cli: false,\n mcp: false,\n})\nexport class FieldPolicySuggestionCollection extends SmrtCollection<FieldPolicySuggestion> {\n static readonly _itemClass = FieldPolicySuggestion;\n\n /**\n * The caller's tenant's PENDING suggestions (optionally filtered to a set\n * of objectRefs), newest first, plus the total for the gear badge.\n */\n async pendingSuggestions(\n options: { objectRefs?: string[] } = {},\n ): Promise<PendingFieldPolicySuggestionsResult> {\n const tenantId = await this.requireManageContext('pendingSuggestions');\n const objectRefs = normalizeObjectRefsFilter(options.objectRefs);\n\n const where: Record<string, unknown> = { tenantId, status: 'pending' };\n if (objectRefs) {\n where['objectRef in'] = objectRefs;\n }\n const rows = await this.list({ where, orderBy: 'created_at DESC' });\n\n return {\n suggestions: rows.map((row) => row.toSuggestionData()),\n total: rows.length,\n };\n }\n\n /**\n * Accept a pending suggestion: CLAIM the pending→accepted transition, then\n * write the corresponding org-scope `FieldPolicy` row THROUGH NORMAL\n * VALIDATION (registry check, type check, security rail, required-field\n * invariant, ownership boundary, and the #2049 permission split — the\n * ambient caller must hold `fields.policy.manage`, which this action also\n * asserts up front).\n *\n * An existing tenant row for the same `(objectRef, fieldName)` is UPDATED\n * (read-modify-write), preserving its other sparse columns — never\n * duplicated through the natural-key upsert.\n *\n * Concurrency: the claim is a compare-and-set on `status` (the jobs\n * `writeOwnedJob` precedent — a guarded UPDATE with `RETURNING id`), so an\n * overlapping accept/dismiss pair cannot both win; the loser throws\n * {@link FieldPolicySuggestionConflictError} (409). Claim and policy write\n * run in ONE transaction when the driver supports it, so a rejected policy\n * write can never leave the suggestion settled without its policy (and vice\n * versa). Drivers without transactions get an explicit compensating revert.\n */\n async acceptSuggestion(\n options: { id?: string } = {},\n ): Promise<AcceptFieldPolicySuggestionResult> {\n const tenantId = await this.requireManageContext('acceptSuggestion');\n const suggestion = await this.loadOwnedPendingSuggestion(\n 'acceptSuggestion',\n options.id,\n tenantId,\n );\n const suggestionId = String(suggestion.id);\n const decidedBy = getCurrentTenant()?.userId ?? null;\n const decidedAt = new Date();\n\n const claim = {\n status: 'accepted' as const,\n activeKey: suggestionId,\n decidedAt,\n decidedBy,\n cooldownUntil: null,\n };\n\n const tx = await this.beginTransactionIfSupported();\n if (tx) {\n let policyRowId: string;\n try {\n const claimed = await claimSuggestionTransition(\n tx,\n suggestionId,\n claim,\n );\n if (!claimed) {\n throw new FieldPolicySuggestionConflictError(\n 'acceptSuggestion',\n suggestionId,\n );\n }\n policyRowId = await this.applyAcceptedPolicy(\n tx,\n suggestion,\n tenantId,\n decidedBy,\n );\n await tx.commit();\n } catch (error) {\n try {\n await tx.rollback();\n } catch {\n // Preserve the original failure; rollback errors are secondary.\n }\n throw error;\n }\n // The policy save inside the transaction invalidated the resolver cache\n // under the TRANSACTION handle's namespace, which is not the app db's —\n // so the committed change would otherwise stay invisible for the cache\n // TTL. Re-invalidate against this collection's db now that it is durable.\n invalidateFieldPolicyCache(suggestion.objectRef, this.db);\n suggestion.status = 'accepted';\n suggestion.decidedAt = decidedAt;\n suggestion.decidedBy = decidedBy;\n return { suggestion: suggestion.toSuggestionData(), policyRowId };\n }\n\n // No transaction support (e.g. a transaction VIEW, which exposes\n // `transaction` but not `beginTransaction`). Claim in TWO steps so the\n // identity's active slot is never free while the decision is in flight:\n //\n // 1. flip `status` to accepted but KEEP `activeKey = 'active'` — the\n // compare-and-set still makes exactly one decision win, while the\n // unique index keeps holding the slot, so generation running in this\n // window cannot insert a fresh pending suggestion that would then\n // collide with the compensation (and be resolved against the stale\n // pre-acceptance policy);\n // 2. only AFTER the policy write succeeds, settle the key to the row's\n // id, releasing the slot for a future post-cool-down regeneration.\n //\n // A refused policy write therefore reverts into a slot nothing else can\n // have taken. Generation's suppression keys off the slot (not `status`)\n // precisely so step 1 suppresses it.\n const claimed = await claimSuggestionTransition(this.db, suggestionId, {\n ...claim,\n activeKey: ACTIVE_SUGGESTION_KEY,\n });\n if (!claimed) {\n throw new FieldPolicySuggestionConflictError(\n 'acceptSuggestion',\n suggestionId,\n );\n }\n let policyRowId: string;\n try {\n policyRowId = await this.applyAcceptedPolicy(\n this.db,\n suggestion,\n tenantId,\n decidedBy,\n );\n } catch (error) {\n // Compensate back to pending. The slot was held throughout, so a\n // collision here is not an expected race — it means the row was mutated\n // underneath us and the compensation did NOT restore it. Surface that\n // instead of swallowing it: the caller must not be told the suggestion\n // is still pending when it may be stuck accepted-without-policy.\n try {\n await revertSuggestionToPending(this.db, suggestionId);\n } catch (revertError) {\n throw new FieldPolicySuggestionCompensationError(\n suggestionId,\n error,\n revertError,\n );\n }\n throw error;\n }\n\n // Release the slot now that the policy is durable.\n await settleSuggestionActiveKey(this.db, suggestionId);\n // The atomic column update bypasses the model, so it does not invalidate\n // the resolver cache the way `FieldPolicy.save()` does — do it here so org\n // forms shift immediately on this path too.\n invalidateFieldPolicyCache(suggestion.objectRef, this.db);\n\n suggestion.status = 'accepted';\n suggestion.decidedAt = decidedAt;\n suggestion.decidedBy = decidedBy;\n return { suggestion: suggestion.toSuggestionData(), policyRowId };\n }\n\n /**\n * Dismiss a pending suggestion: set status + a cool-down until which the\n * generation job will NOT regenerate the same\n * `(objectRef, fieldName, tenantId, kind)` suggestion.\n *\n * Validates OWNERSHIP and PENDING STATUS ONLY — deliberately NOT continued\n * proposal eligibility. A queued suggestion whose field has since been\n * removed, turned sensitive/`readPermission`-gated/transient, or retyped is\n * exactly the row an operator most needs to clear; re-running proposal\n * validation on the way out would reject the dismissal and, because pending\n * rows are never pruned, wedge it in the queue forever. The claim is the same\n * compare-and-set as accept, so it also bypasses the model's proposal\n * validation by construction.\n */\n async dismissSuggestion(\n options: { id?: string; coolDownMs?: number } = {},\n ): Promise<DismissFieldPolicySuggestionResult> {\n const tenantId = await this.requireManageContext('dismissSuggestion');\n const suggestion = await this.loadOwnedPendingSuggestion(\n 'dismissSuggestion',\n options.id,\n tenantId,\n );\n const suggestionId = String(suggestion.id);\n const coolDownMs = normalizeCoolDownMs(options.coolDownMs);\n const decidedAt = new Date();\n const decidedBy = getCurrentTenant()?.userId ?? null;\n const cooldownUntil = new Date(decidedAt.getTime() + coolDownMs);\n\n const claimed = await claimSuggestionTransition(this.db, suggestionId, {\n status: 'dismissed',\n activeKey: suggestionId,\n decidedAt,\n decidedBy,\n cooldownUntil,\n });\n if (!claimed) {\n throw new FieldPolicySuggestionConflictError(\n 'dismissSuggestion',\n suggestionId,\n );\n }\n\n suggestion.status = 'dismissed';\n suggestion.cooldownUntil = cooldownUntil;\n suggestion.decidedAt = decidedAt;\n suggestion.decidedBy = decidedBy;\n return { suggestion: suggestion.toSuggestionData() };\n }\n\n /**\n * Write the org-scope policy column an accepted suggestion implies, bound to\n * `db` so it participates in the caller's transaction.\n *\n * A `promote` and a `default` suggestion for the SAME field share one sparse\n * `FieldPolicy` row but own DIFFERENT columns, and both may be accepted\n * concurrently. A read-modify-save of the whole row therefore loses updates:\n * each acceptance reads the row (or its absence) before the other's save\n * lands, and the later full-row write erases the sibling's column — and, via\n * the natural-key upsert, can even replace the row id, dangling the earlier\n * caller's `policyRowId`. (Distinct from the suggestion-row race fixed\n * earlier: that one guards the pending→settled transition; this one is on the\n * shared policy row.) So this NEVER writes a whole row over an existing one:\n *\n * 1. An ATOMIC partial UPDATE sets only this acceptance's column, keyed by\n * the policy natural key and `RETURNING id` — one statement, so no\n * interleaving can drop the sibling's column, and the returned id is\n * always the surviving row.\n * 2. Only when no row exists yet does it CREATE through the model, so full\n * validation (registry, type check, security rail, required-field\n * invariant, ownership + permission split) runs on the row that is\n * actually inserted. The row carries a DETERMINISTIC id under strict\n * insert, so two concurrent creates cannot each mint a row: the loser\n * collides and falls back to step 1, applying only its own column.\n *\n * The update path skips the model, so BOTH kinds re-check live target\n * addressability through the shared helpers (a field can turn sensitive,\n * gated, transient, or disappear between queueing and acceptance). Defaults\n * additionally re-check their value/type rail; a promotion to `basic` has no\n * value payload, and the required-field invariant restricts only\n * advanced/hidden. Org locks constrain the user tier, not org rows.\n */\n private async applyAcceptedPolicy(\n db: DatabaseInterface,\n suggestion: FieldPolicySuggestion,\n tenantId: string,\n decidedBy: string | null,\n ): Promise<string> {\n if (suggestion.kind === 'default') {\n await assertAcceptedDefaultStillAllowed(suggestion);\n } else {\n await assertAcceptedPolicyTargetStillAllowed(suggestion);\n }\n\n const target = {\n objectRef: suggestion.objectRef,\n fieldName: suggestion.fieldName,\n tenantId,\n column:\n suggestion.kind === 'promote'\n ? ('visibility' as const)\n : ('default_value' as const),\n value: suggestion.kind === 'promote' ? 'basic' : suggestion.proposedValue,\n decidedBy,\n };\n\n const updatedId = await updatePolicyColumn(db, target);\n if (updatedId) {\n return updatedId;\n }\n\n // Dynamic import breaks the module cycle risk with the policy model\n // family (mirrors the resolver/collection seams in this package).\n const { FieldPolicyCollection } = await import(\n './FieldPolicyCollection.js'\n );\n const policies = await FieldPolicyCollection.create(\n transactionBoundOptions(db),\n );\n const deterministicId = await fieldPolicyRowId(\n tenantId,\n suggestion.objectRef,\n suggestion.fieldName,\n );\n\n try {\n const created = await policies.create({\n id: deterministicId,\n objectRef: suggestion.objectRef,\n fieldName: suggestion.fieldName,\n scopeType: 'tenant',\n tenantId,\n ...(suggestion.kind === 'promote'\n ? { visibility: 'basic' as const }\n : { defaultValue: suggestion.proposedValue }),\n updatedBy: decidedBy,\n // Strict insert: a concurrent acceptance that already created the row\n // must NOT be adopted-and-overwritten (that is the lost update).\n _insertOnly: true,\n });\n return String(created.id);\n } catch (error) {\n // Someone created the row between the update and the insert. Apply just\n // this acceptance's column to the surviving row.\n const racedId = await updatePolicyColumn(db, target);\n if (racedId) {\n return racedId;\n }\n throw error;\n }\n }\n\n /**\n * A driver transaction handle, or `null` when the driver has none (the\n * `FieldPolicy.saveAfterIdentityChange` probe, same shape).\n */\n private async beginTransactionIfSupported(): Promise<SuggestionTransactionHandle | null> {\n if (typeof this.db.beginTransaction !== 'function') {\n return null;\n }\n const tx = (await this.db.beginTransaction()) as\n | SuggestionTransactionHandle\n | undefined;\n return tx ?? null;\n }\n\n /**\n * Ambient manage gate shared by the three actions: an ambient tenant\n * context is required (no context ⇒ no identity ⇒ fail closed — the\n * suggestion queue is never an anonymous surface), and the caller must hold\n * `fields.policy.manage` unless running under super-admin bypass.\n */\n private async requireManageContext(action: string): Promise<string> {\n const context = getCurrentTenant();\n if (!context?.tenantId) {\n throw new FieldPolicySuggestionRequestContextError(\n `${action} requires an ambient tenant context (fail closed): the ` +\n 'suggestion queue is scoped to the caller tenant',\n );\n }\n if (!isSuperAdminBypass()) {\n await assertOperationPermission({\n collection: 'fields.policy',\n action: MANAGE_FIELD_POLICY_PERMISSION.split('.').at(-1) ?? 'manage',\n db: this.db,\n tenantId: context.tenantId,\n userId: context.userId ?? null,\n permissionSet: context.permissions,\n });\n }\n return context.tenantId;\n }\n\n private async loadOwnedPendingSuggestion(\n action: string,\n id: string | undefined,\n tenantId: string,\n ): Promise<FieldPolicySuggestion> {\n if (typeof id !== 'string' || id.trim() === '') {\n throw new Error(`${action} requires a suggestion \"id\" string`);\n }\n const suggestion = await this.get(id);\n if (!suggestion) {\n throw new Error(`${action}: no suggestion found for id \"${id}\"`);\n }\n if (suggestion.tenantId !== tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in ${action}: the suggestion belongs to ` +\n `another tenant`,\n {\n tenantId,\n attemptedTenantId: suggestion.tenantId ?? undefined,\n },\n );\n }\n if (suggestion.status !== 'pending') {\n // The SAME conflict the compare-and-set raises: whether a competing\n // decision landed before this request read the row or after, the caller\n // sees one error type (and one status) instead of a different failure\n // per interleaving.\n throw new FieldPolicySuggestionConflictError(action, id);\n }\n return suggestion;\n }\n}\n\nfunction normalizeObjectRefsFilter(\n rawRefs: string[] | undefined,\n): string[] | null {\n if (rawRefs === undefined) {\n return null;\n }\n if (!Array.isArray(rawRefs) || rawRefs.length === 0) {\n throw new Error(\n 'pendingSuggestions \"objectRefs\" must be a non-empty string array when provided',\n );\n }\n if (rawRefs.some((ref) => typeof ref !== 'string' || ref.trim() === '')) {\n throw new Error('pendingSuggestions objectRefs must be non-empty strings');\n }\n const objectRefs = [...new Set(rawRefs)];\n if (objectRefs.length > MAX_PENDING_OBJECT_REFS) {\n throw new Error(\n `pendingSuggestions accepts at most ${MAX_PENDING_OBJECT_REFS} ` +\n `objectRefs per call (got ${objectRefs.length})`,\n );\n }\n return objectRefs;\n}\n\nfunction normalizeCoolDownMs(raw: number | undefined): number {\n if (raw === undefined) {\n return DEFAULT_SUGGESTION_COOL_DOWN_MS;\n }\n if (typeof raw !== 'number' || !Number.isFinite(raw)) {\n throw new Error('dismissSuggestion coolDownMs must be a finite number');\n }\n return Math.min(\n Math.max(raw, MIN_SUGGESTION_COOL_DOWN_MS),\n MAX_SUGGESTION_COOL_DOWN_MS,\n );\n}\n","import {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\n\n/**\n * Cap on distinct-user ids stored per bucket. For threshold questions\n * (\"did at least N distinct users set this field?\") the capped set is EXACT up\n * to the cap; once overflowed, {@link FieldUsageCounter.distinctUserCount} is\n * an honest LOWER BOUND that trivially satisfies any threshold ≤ the cap.\n */\nexport const MAX_DISTINCT_USERS_PER_BUCKET = 100;\n\n/** Cap on histogram buckets per counter row (bounded storage). */\nexport const MAX_VALUE_HISTOGRAM_BUCKETS = 25;\n\n/** Longest histogram key recorded; longer samples are skipped (count-only). */\nexport const MAX_VALUE_HISTOGRAM_KEY_LENGTH = 64;\n\n/** `period` bucket format: UTC calendar day. */\nexport const FIELD_USAGE_PERIOD_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** The UTC day bucket for a timestamp (`YYYY-MM-DD`). */\nexport function fieldUsagePeriodForDate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\n/**\n * A prototype-free histogram map.\n *\n * Histogram keys are user-supplied values (an `idType: 'text'` reference id may\n * legitimately be `constructor`, `toString`, or `__proto__`). On a plain object\n * those either resolve to inherited members — making an absent bucket look\n * present and corrupting its count — or, for `__proto__`, invoke the prototype\n * setter instead of creating an own key. A null-prototype object has no such\n * members, so every key behaves like data. Use this everywhere histogram counts\n * are accumulated (storage AND merge paths).\n */\nexport function emptyHistogram(): Record<string, number> {\n return Object.create(null) as Record<string, number>;\n}\n\nexport interface FieldUsageCounterOptions extends SmrtObjectOptions {\n objectRef?: string;\n fieldName?: string;\n tenantId?: string;\n period?: string;\n submissionCount?: number;\n setCount?: number;\n distinctUserCount?: number;\n distinctUserIds?: string;\n distinctUsersOverflowed?: boolean;\n valueHistogram?: string | null;\n valueHistogramOverflowed?: boolean;\n}\n\n/**\n * Period-bucketed field usage counter (epic #2045, issue #2051).\n *\n * One row aggregates field submissions for a single\n * `(objectRef, fieldName, tenantId, period)` — the substrate the\n * suggestion-generation job reads. Counters are deliberately APPROXIMATE:\n * ingestion is fire-and-forget and concurrent bucket merges may lose an\n * increment (read-modify-write), which is acceptable for usage statistics and\n * documented here rather than papered over.\n *\n * TWO counters, because they answer different questions:\n * - {@link submissionCount} — EVERY observed submission of the field\n * (default-matching or not). It is the denominator for value dominance:\n * \"N% of submissions used value V\". Without it, a value seen only in\n * deviations would look 100% dominant even against thousands of\n * default-valued submissions.\n * - {@link setCount} — submissions whose value DIFFERED from the resolved\n * default (server-derived). It plus {@link distinctUserIds} is the\n * promote signal (\"real users are actively filling this in\").\n *\n * Content rails (enforced by the ingestion action, which derives everything\n * from the live registry and never trusts the client):\n * - Sensitive and read-permission-gated fields are COUNT-ONLY: their raw\n * values are never recorded anywhere in usage data — not even for\n * default-matching submissions.\n * - Value histograms exist only for low-cardinality field types (`boolean`,\n * `foreignKey`, `crossPackageRef`) — never free text, even non-sensitive\n * text (PII risk) — with a bounded bucket count and key length. They cover\n * ALL submissions (not just deviations) so the dominance ratio is a true\n * fraction of {@link submissionCount}.\n * - `distinctUserIds` is a capped set with an overflow marker (see\n * {@link MAX_DISTINCT_USERS_PER_BUCKET} for the honesty contract).\n */\n// All generated surfaces are CLOSED: rows aggregate cross-tenant usage, so\n// list/get would leak other tenants' activity and create/update/delete would\n// let clients forge counters. The ONLY write path is the collection's\n// `reportUsage` action (ambient-identity, fail closed); reads happen\n// server-side in the learning jobs.\n@smrt({\n tableName: '_smrt_field_usage_counters',\n conflictColumns: ['object_ref', 'field_name', 'tenant_id', 'period'],\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldUsageCounter extends SmrtObject {\n /** Qualified class name of the target object (`@package/name:ClassName`). */\n @field({ required: true })\n objectRef: string = '';\n\n /** Field name on the target object. */\n @field({ required: true })\n fieldName: string = '';\n\n /**\n * Owning tenant. REQUIRED: ingestion fails closed without an ambient tenant\n * context, so every row is attributable (and the conflict-column tuple\n * stays total). Native UUID on PostgreSQL/DuckDB.\n */\n @tenantId()\n tenantId?: string;\n\n /** UTC day bucket (`YYYY-MM-DD`); lexicographic order is time order. */\n @field({ required: true })\n period: string = '';\n\n /**\n * EVERY observed submission of this field in the bucket, whether or not the\n * value matched the resolved default — the dominance denominator.\n *\n * Rows written before this column existed carry `0` while `setCount > 0`;\n * {@link isLegacyBucket} detects that shape and the generation job then\n * treats the total as UNKNOWN and skips `default` suggestions for the group\n * (promote, which needs no denominator, still works).\n */\n @field({ type: 'integer' })\n submissionCount: number = 0;\n\n /**\n * Submissions whose value DIFFERED from the server-resolved default (the\n * promote signal). Always `<= submissionCount` on rows written by the\n * current ingestion path.\n */\n @field({ type: 'integer' })\n setCount: number = 0;\n\n /**\n * Size of the stored distinct-user set. When\n * {@link distinctUsersOverflowed} is true this is a LOWER BOUND (the set is\n * capped), never an estimate.\n */\n @field({ type: 'integer' })\n distinctUserCount: number = 0;\n\n /** JSON array of distinct user ids, capped (see the class doc). */\n @field({ type: 'text' })\n distinctUserIds: string = '[]';\n\n /** True once a distinct user was NOT added because the set is at its cap. */\n @field({ type: 'boolean' })\n distinctUsersOverflowed: boolean = false;\n\n /**\n * JSON object `serializedValue -> count` for histogram-eligible fields;\n * NULL when the field is count-only. Keys are bounded in number and length.\n */\n @field({ type: 'text', nullable: true })\n valueHistogram: string | null = null;\n\n /** True once a sample was dropped because the bucket cap was reached. */\n @field({ type: 'boolean' })\n valueHistogramOverflowed: boolean = false;\n\n constructor(options: FieldUsageCounterOptions = {}) {\n super(options);\n if (options.objectRef !== undefined) this.objectRef = options.objectRef;\n if (options.fieldName !== undefined) this.fieldName = options.fieldName;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.period !== undefined) this.period = options.period;\n if (options.submissionCount !== undefined) {\n this.submissionCount = options.submissionCount;\n }\n if (options.setCount !== undefined) this.setCount = options.setCount;\n if (options.distinctUserCount !== undefined) {\n this.distinctUserCount = options.distinctUserCount;\n }\n if (options.distinctUserIds !== undefined) {\n this.distinctUserIds = options.distinctUserIds;\n }\n if (options.distinctUsersOverflowed !== undefined) {\n this.distinctUsersOverflowed = options.distinctUsersOverflowed;\n }\n if (options.valueHistogram !== undefined) {\n this.valueHistogram = options.valueHistogram;\n }\n if (options.valueHistogramOverflowed !== undefined) {\n this.valueHistogramOverflowed = options.valueHistogramOverflowed;\n }\n }\n\n /** Parse the stored distinct-user set (guarded; junk parses as empty). */\n getDistinctUserIds(): string[] {\n try {\n const parsed = JSON.parse(this.distinctUserIds);\n return Array.isArray(parsed)\n ? parsed.filter((id): id is string => typeof id === 'string')\n : [];\n } catch {\n return [];\n }\n }\n\n /**\n * Add a user to the distinct set, honoring the cap. At the cap the id is\n * NOT added and the overflow marker is set instead, keeping\n * {@link distinctUserCount} an honest lower bound.\n */\n addDistinctUser(userId: string): void {\n const ids = this.getDistinctUserIds();\n if (ids.includes(userId)) {\n return;\n }\n if (ids.length >= MAX_DISTINCT_USERS_PER_BUCKET) {\n this.distinctUsersOverflowed = true;\n return;\n }\n ids.push(userId);\n this.distinctUserIds = JSON.stringify(ids);\n this.distinctUserCount = ids.length;\n }\n\n /**\n * Parse the stored histogram (guarded; junk parses as empty).\n *\n * Returns a NULL-PROTOTYPE object. Histogram keys are user-supplied ids —\n * an `idType: 'text'` reference may legitimately be `constructor`,\n * `toString`, or `__proto__` — and on a plain object those inherit truthy\n * prototype values (so a missing bucket reads as present) or, for\n * `__proto__`, hit the prototype setter instead of creating an own key.\n * Both would silently corrupt counts. See {@link emptyHistogram}.\n */\n getValueHistogram(): Record<string, number> {\n const histogram = emptyHistogram();\n if (!this.valueHistogram) {\n return histogram;\n }\n try {\n const parsed = JSON.parse(this.valueHistogram);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return histogram;\n }\n // `Object.entries` yields OWN enumerable keys only, and `JSON.parse`\n // materializes `__proto__` as an ordinary own data property, so the\n // round trip preserves every legitimate id.\n for (const [key, count] of Object.entries(parsed)) {\n if (typeof count === 'number' && Number.isFinite(count) && count > 0) {\n histogram[key] = count;\n }\n }\n return histogram;\n } catch {\n return emptyHistogram();\n }\n }\n\n /**\n * Record one histogram sample under an already-serialized key, honoring the\n * bucket cap (a NEW key past the cap is dropped and the overflow marker\n * set; existing keys keep counting).\n *\n * Bucket presence is an OWN-key test, never a truthiness/`undefined` read,\n * so prototype-shaped ids behave like any other key.\n */\n recordHistogramSample(key: string): void {\n if (key.length === 0 || key.length > MAX_VALUE_HISTOGRAM_KEY_LENGTH) {\n return;\n }\n const histogram = this.getValueHistogram();\n if (!Object.hasOwn(histogram, key)) {\n if (Object.keys(histogram).length >= MAX_VALUE_HISTOGRAM_BUCKETS) {\n this.valueHistogramOverflowed = true;\n return;\n }\n histogram[key] = 1;\n } else {\n histogram[key] += 1;\n }\n this.valueHistogram = JSON.stringify(histogram);\n }\n\n /**\n * Whether this bucket predates the {@link submissionCount} column (or was\n * corrupted): it records deviations without a total, so no honest dominance\n * ratio can be computed from it. The generation job skips `default`\n * suggestions for any group containing such a bucket.\n */\n isLegacyBucket(): boolean {\n return this.submissionCount < this.setCount;\n }\n\n override async save(): Promise<this> {\n await this.assertRowOwnedByAmbientContext('save');\n this.validateFieldUsageCounter();\n return super.save();\n }\n\n override async delete(): Promise<void> {\n await this.assertRowOwnedByAmbientContext('delete');\n await super.delete();\n }\n\n private validateFieldUsageCounter(): void {\n if (!this.objectRef || this.objectRef.trim() === '') {\n throw new Error('FieldUsageCounter.objectRef is required');\n }\n if (!this.fieldName || this.fieldName.trim() === '') {\n throw new Error('FieldUsageCounter.fieldName is required');\n }\n if (!this.tenantId) {\n throw new Error('FieldUsageCounter.tenantId is required');\n }\n if (!FIELD_USAGE_PERIOD_PATTERN.test(this.period)) {\n throw new Error(\n `FieldUsageCounter.period must be a UTC day bucket (YYYY-MM-DD); ` +\n `got \"${this.period}\"`,\n );\n }\n if (!Number.isInteger(this.submissionCount) || this.submissionCount < 0) {\n throw new Error(\n 'FieldUsageCounter.submissionCount must be a non-negative integer',\n );\n }\n if (!Number.isInteger(this.setCount) || this.setCount < 0) {\n throw new Error(\n 'FieldUsageCounter.setCount must be a non-negative integer',\n );\n }\n if (\n !Number.isInteger(this.distinctUserCount) ||\n this.distinctUserCount < 0\n ) {\n throw new Error(\n 'FieldUsageCounter.distinctUserCount must be a non-negative integer',\n );\n }\n }\n\n /**\n * Tenant write boundary (the FieldPolicy posture — no class-level\n * `@TenantScoped`, because the learning jobs legitimately operate\n * cross-tenant in trusted execution): inside a non-bypass tenant context a\n * caller may only touch rows of its own tenant; without a context (system/\n * job execution) writes are trusted.\n *\n * Checked against BOTH the in-memory tenant and — for a row that already\n * exists — the PERSISTED one. The persisted check is what makes the boundary\n * real: bucket ids are deterministic and the deriving helper is exported, so\n * a foreign row is trivially addressable, and an in-memory-only check would\n * let a caller load it, re-stamp `tenantId` with its own, and adopt or delete\n * another tenant's counters (the #2047 FieldPolicy pattern).\n */\n private async assertRowOwnedByAmbientContext(\n operation: 'save' | 'delete',\n ): Promise<void> {\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return;\n }\n if (this.tenantId !== context.tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldUsageCounter.${operation}: ` +\n `context tenant is '${context.tenantId}' but the row belongs to ` +\n `'${this.tenantId}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: this.tenantId ?? undefined,\n },\n );\n }\n\n const persistedTenantId = await this.getPersistedTenantId();\n if (persistedTenantId !== null && persistedTenantId !== context.tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldUsageCounter.${operation}: the ` +\n `persisted row belongs to '${persistedTenantId}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: persistedTenantId,\n },\n );\n }\n }\n\n /** The stored tenant for this row's id; `null` when it is not persisted. */\n private async getPersistedTenantId(): Promise<string | null> {\n if (!this.id) {\n return null;\n }\n const existing = await this.db.get(this.tableName, { id: this.id });\n if (!existing) {\n return null;\n }\n const row = existing as Record<string, unknown>;\n const value = row.tenantId ?? row.tenant_id;\n return value === undefined || value === null ? null : String(value);\n }\n}\n","import {\n crossPackageRef,\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { tenantId } from '@happyvertical/smrt-tenancy';\n\n/**\n * Durable daily receipt for one member's contribution to one field.\n *\n * The counter action creates this before incrementing its aggregate. Its\n * natural key makes the anti-inflation rule durable across requests and\n * replicas: one `(tenant, user, object, field, UTC day)` sample may affect\n * usage evidence. Receipts intentionally retain no submitted value.\n */\n@smrt({\n tableName: '_smrt_field_usage_report_receipts',\n conflictColumns: [\n 'tenant_id',\n 'user_id',\n 'object_ref',\n 'field_name',\n 'period',\n ],\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldUsageReportReceipt extends SmrtObject {\n @tenantId()\n tenantId?: string;\n\n @crossPackageRef('@happyvertical/smrt-users:User')\n userId: string = '';\n\n @field({ required: true })\n objectRef: string = '';\n\n @field({ required: true })\n fieldName: string = '';\n\n @field({ required: true })\n period: string = '';\n\n constructor(options: FieldUsageReportReceiptOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.objectRef !== undefined) this.objectRef = options.objectRef;\n if (options.fieldName !== undefined) this.fieldName = options.fieldName;\n if (options.period !== undefined) this.period = options.period;\n }\n}\n\nexport interface FieldUsageReportReceiptOptions extends SmrtObjectOptions {\n tenantId?: string;\n userId?: string;\n objectRef?: string;\n fieldName?: string;\n period?: string;\n}\n","import { SmrtCollection, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { deterministicFieldsUuid } from '../deterministic-id.js';\nimport {\n type FieldDefinitionMap,\n getFieldReadPermission,\n getObjectFieldMap,\n isSensitiveField,\n isStorableReferenceId,\n isTransientField,\n type RegisteredFieldInfo,\n} from '../field-definitions.js';\nimport {\n FieldUsageCounter,\n fieldUsagePeriodForDate,\n MAX_VALUE_HISTOGRAM_KEY_LENGTH,\n} from '../models/FieldUsageCounter.js';\nimport { FieldUsageReportReceipt } from '../models/FieldUsageReportReceipt.js';\nimport type {\n FieldUsageReportEntry,\n FieldUsageReportResult,\n ResolvedFieldPolicy,\n} from '../types.js';\n\n/**\n * Deviation comparison: strict equality, then a JSON round-trip so structured\n * and Date-shaped values compare by serialization. Mirrors the browser-side\n * `fieldUsageValuesEqual` (the packaging boundary bars value imports across the\n * subpath split — the permission-slug precedent); a node test pins the two\n * behaviours equal.\n */\nexport function usageValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true;\n }\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n}\n\n/**\n * Per-call bound on reported entries — the rate rail for the fire-and-forget\n * ingestion action (the resolveBatch precedent). The browser deliberately\n * does not impose a duplicate limit: every report is server-bounded here.\n */\nexport const MAX_USAGE_REPORT_ENTRIES = 100;\n\n/** Expected generated-route denial for a missing trusted report principal. */\nclass FieldUsageRequestContextError extends TenantIsolationError {\n readonly httpStatus = 403;\n readonly status = 403;\n\n constructor(message: string, details?: { tenantId?: string }) {\n super(message, details);\n this.name = 'FieldUsageRequestContextError';\n }\n}\n\n/**\n * Collection surface for {@link FieldUsageCounter} plus the batched usage\n * ingestion action (#2051).\n *\n * `reportUsage` is a custom collection-scoped action (the resolveBatch\n * mechanism — single-segment path, so the generated SvelteKit transport AND\n * core's runtime `APIGenerator` both dispatch it). Everything else is CLOSED:\n * no generated CRUD, no CLI, no MCP — counters are written only through this\n * action and read only by trusted server-side code (the learning jobs).\n */\n@smrt({\n // A decorated collection emits its own schema for the item's table. Mirror\n // the model natural key so manifest-driven migrations cannot add the\n // fallback `(slug, context)` unique index to this shared system table.\n conflictColumns: ['object_ref', 'field_name', 'tenant_id', 'period'],\n api: {\n include: ['reportUsage'],\n // The action derives tenant/user exclusively from the authenticated\n // request context; generated routes must establish that context before\n // dispatching it (the FieldPolicyCollection precedent).\n principalContext: true,\n routes: {\n reportUsage: {\n scope: 'collection',\n method: 'POST',\n path: 'report',\n },\n },\n },\n cli: false,\n mcp: false,\n})\nexport class FieldUsageCounterCollection extends SmrtCollection<FieldUsageCounter> {\n static readonly _itemClass = FieldUsageCounter;\n\n /**\n * Record a batch of field submissions into the current UTC-day counters.\n *\n * Identity is AMBIENT-ONLY (the resolveBatch posture): the tenant AND the\n * user come exclusively from the tenant context established by the app's\n * auth hook — the request body cannot attribute usage to another tenant or\n * user. Both are REQUIRED and the call fails closed without them (#2047\n * round-3 posture):\n *\n * - No ambient tenant ⇒ the report is unattributable.\n * - No ambient USER ⇒ the caller is an unauthenticated principal (a\n * deployment that resolves the tenant from host/header alone). Such\n * callers could otherwise inflate counts and histograms without bound\n * (the batch cap is per call), and `distinctUsers` — the promote signal —\n * is meaningless without a principal. Consequence, accepted deliberately:\n * ANONYMOUS/PUBLIC FORMS DO NOT CONTRIBUTE USAGE. The learning loop is\n * for authenticated org users.\n *\n * Any authenticated in-tenant principal may report (no manage slug needed).\n * A durable receipt admits at most one `(tenant, user, object, field, UTC\n * day)` contribution, and the batch stays bounded per call\n * ({@link MAX_USAGE_REPORT_ENTRIES}).\n *\n * Server-side derivation (the client is never the authority):\n * - Fields are validated against the live `ObjectRegistry`; unknown or\n * non-usage-addressable entries (system, relationship pseudo-fields, STI\n * meta storage, transient) are DROPPED and counted, never failing the\n * batch (stale clients after a redeploy are expected).\n * - Sensitivity comes from BOTH `field.sensitive` and `field._meta.sensitive`\n * (and `readPermission` in both places): such fields are recorded\n * COUNT-ONLY — their values are never persisted anywhere in usage data,\n * default-matching or not.\n * - DEVIATION (`setCount`, distinct users) is decided by comparing the\n * submitted value against the default RESOLVED HERE for the calling\n * identity — never against a client claim. Every accepted entry also\n * increments `submissionCount`, the dominance denominator.\n * - Values are histogrammed only for low-cardinality field types\n * (`boolean`, `foreignKey`, `crossPackageRef`) with type-checked samples;\n * free text is never histogrammed.\n *\n * Bounded trust in `matchedDefault`: an entry may omit `value` entirely\n * (`collectFieldUsageEntries({ includeValues: false })` — apps that prefer\n * no value transit). Only then is the entry's `matchedDefault` flag read, to\n * decide the deviation bit alone. It can never create a histogram entry,\n * never bypass the count-only rail, and never manufacture distinct users\n * beyond the caller's own id. The once-per-field/day receipt means a lying\n * client gets only one such contribution, so repeated POSTs cannot inflate\n * thresholds or steer a `default` suggestion.\n */\n async reportUsage(\n options: { entries?: FieldUsageReportEntry[] } = {},\n ): Promise<FieldUsageReportResult> {\n const entries = validateEntriesInput(options.entries);\n\n const context = getCurrentTenant();\n const tenantId = context?.tenantId;\n if (!tenantId) {\n throw new FieldUsageRequestContextError(\n 'reportUsage requires an ambient tenant context: usage is ' +\n 'unattributable without one, so the call fails closed',\n );\n }\n const userId = context.userId;\n if (!userId) {\n throw new FieldUsageRequestContextError(\n 'reportUsage requires an ambient AUTHENTICATED user: an ' +\n 'unauthenticated caller could inflate counters without bound and ' +\n 'distinct-user thresholds are meaningless without a principal, so ' +\n 'the call fails closed (anonymous forms do not contribute usage)',\n { tenantId },\n );\n }\n const period = fieldUsagePeriodForDate(new Date());\n const receipts = await FieldUsageReportReceiptCollection.create({\n db: this.db,\n });\n\n // Group by (objectRef, fieldName) so one bucket row is touched once per\n // batch (bounds registry and db work per call).\n const groups = new Map<string, Map<string, FieldUsageReportEntry[]>>();\n for (const entry of entries) {\n let byField = groups.get(entry.objectRef);\n if (!byField) {\n byField = new Map<string, FieldUsageReportEntry[]>();\n groups.set(entry.objectRef, byField);\n }\n const samples = byField.get(entry.fieldName);\n if (samples) {\n samples.push(entry);\n } else {\n byField.set(entry.fieldName, [entry]);\n }\n }\n\n let accepted = 0;\n let dropped = 0;\n\n for (const [objectRef, byField] of groups) {\n let fieldMap: FieldDefinitionMap;\n try {\n fieldMap = await getObjectFieldMap(objectRef);\n } catch {\n for (const samples of byField.values()) {\n dropped += samples.length;\n }\n continue;\n }\n\n const resolvedFields = await this.resolveDefaultsForCaller(\n objectRef,\n tenantId,\n userId,\n );\n if (!resolvedFields) {\n // Defaults are the deviation authority; without them every entry\n // would have to be guessed. Drop the group rather than guess.\n for (const samples of byField.values()) {\n dropped += samples.length;\n }\n continue;\n }\n\n for (const [fieldName, samples] of byField) {\n const fieldDef = fieldMap.get(fieldName);\n if (!fieldDef || !isUsageAddressableField(fieldDef)) {\n dropped += samples.length;\n continue;\n }\n\n // The durable receipt is the rate rail: at most ONE sample from this\n // user can influence this field's evidence per UTC day, even across\n // repeated requests or concurrent app replicas. Claim it only after\n // registry validation, so a stale/unknown field cannot burn a valid\n // field's daily allowance. Repeated reports are intentionally ignored\n // (not reported as `dropped`, which is reserved for stale entries).\n if (\n !(await receipts.claim({\n tenantId,\n userId,\n objectRef,\n fieldName,\n period,\n }))\n ) {\n continue;\n }\n\n // Batches can carry more than one value for a field, but the same\n // daily rule applies within one request too. The first sample is the\n // one durable contribution for this member/day/field.\n const sample = samples[0];\n\n const countOnly =\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined;\n const histogramEligible =\n !countOnly && isHistogramEligibleField(fieldDef);\n const resolved = resolvedFields[fieldName];\n const hasDefault = resolved?.hasDefault === true;\n const defaultValue = hasDefault ? resolved?.defaultValue : undefined;\n\n let deviations = 0;\n const histogramKeys: string[] = [];\n if ('value' in sample) {\n // Server-derived deviation: the resolved default is the authority.\n if (!hasDefault || !usageValuesEqual(sample.value, defaultValue)) {\n deviations += 1;\n }\n if (histogramEligible) {\n const key = serializeHistogramSample(fieldDef, sample.value);\n if (key !== null) {\n histogramKeys.push(key);\n }\n }\n } else if (sample.matchedDefault !== true) {\n // Value-less entry: only the deviation bit is taken from the\n // client hint (see the bounded-trust note above).\n deviations += 1;\n }\n\n await this.mergeIntoBucket({\n objectRef,\n fieldName,\n tenantId,\n period,\n userId,\n submissionCount: 1,\n deviationCount: deviations,\n histogramKeys,\n });\n accepted += 1;\n }\n }\n\n return { accepted, dropped };\n }\n\n /**\n * The defaults the CALLER's forms would have prefilled — the deviation\n * authority. Resolved for the ambient `(tenant, user)` identity (the\n * resolver's own TTL cache keeps repeat batches cheap).\n *\n * Ingestion is fire-and-forget, so a stored-layer read failure degrades to\n * the CODE-SEED-only resolution (no db) rather than failing the request;\n * `null` (both attempts failed) makes the caller drop the group.\n */\n private async resolveDefaultsForCaller(\n objectRef: string,\n tenantId: string,\n userId: string,\n ): Promise<Record<string, ResolvedFieldPolicy> | undefined | null> {\n // Dynamic import mirrors the resolver seams elsewhere in this package\n // (the resolver statically imports the policy collection).\n const { resolveFieldPolicy } = await import('../field-policy-resolver.js');\n try {\n const resolved = await resolveFieldPolicy(objectRef, {\n tenantId,\n userId,\n db: this.db,\n });\n return resolved.fields;\n } catch {\n try {\n const seedOnly = await resolveFieldPolicy(objectRef, {\n tenantId,\n userId,\n });\n return seedOnly.fields;\n } catch {\n return null;\n }\n }\n }\n\n /**\n * Counter rows within a period window (inclusive bounds, lexicographic ISO\n * day comparison), optionally restricted to one tenant — the read the\n * suggestion-generation job consumes.\n */\n async listWindow(options: {\n fromPeriod: string;\n toPeriod?: string;\n tenantId?: string | null;\n }): Promise<FieldUsageCounter[]> {\n const where: Record<string, unknown> = {\n 'period >=': options.fromPeriod,\n };\n if (options.toPeriod) {\n where['period <='] = options.toPeriod;\n }\n if (options.tenantId) {\n where.tenantId = options.tenantId;\n }\n return this.list({ where, orderBy: 'period ASC' });\n }\n\n /**\n * Idempotent read-modify-write merge into the deterministic day bucket.\n * Counters are approximate by design: concurrent cross-process merges may\n * lose an increment (documented on the model), while the deterministic id\n * plus the natural-key unique index keep concurrent creates converging on\n * one row.\n */\n private async mergeIntoBucket(options: {\n objectRef: string;\n fieldName: string;\n tenantId: string;\n period: string;\n userId: string;\n /** Every observed submission (the dominance denominator). */\n submissionCount: number;\n /** Submissions that differed from the resolved default. */\n deviationCount: number;\n histogramKeys: string[];\n }): Promise<void> {\n const id = await fieldUsageCounterId(\n options.tenantId,\n options.objectRef,\n options.fieldName,\n options.period,\n );\n\n const existing = await this.get(id);\n const counter =\n existing ??\n new FieldUsageCounter({\n db: this.db,\n id,\n objectRef: options.objectRef,\n fieldName: options.fieldName,\n tenantId: options.tenantId,\n period: options.period,\n });\n\n counter.submissionCount += options.submissionCount;\n counter.setCount += options.deviationCount;\n // Distinct users track the PROMOTE signal: only a caller who actually set\n // a non-default value counts as \"actively filling this field in\".\n if (options.deviationCount > 0) {\n counter.addDistinctUser(options.userId);\n }\n for (const key of options.histogramKeys) {\n counter.recordHistogramSample(key);\n }\n if (!existing) {\n await counter.initialize();\n }\n await counter.save();\n }\n}\n\n/** Internal only — this table has no generated surface. */\n@smrt({\n conflictColumns: [\n 'tenant_id',\n 'user_id',\n 'object_ref',\n 'field_name',\n 'period',\n ],\n api: false,\n cli: false,\n mcp: false,\n})\nclass FieldUsageReportReceiptCollection extends SmrtCollection<FieldUsageReportReceipt> {\n static readonly _itemClass = FieldUsageReportReceipt;\n\n async claim(options: {\n tenantId: string;\n userId: string;\n objectRef: string;\n fieldName: string;\n period: string;\n }): Promise<boolean> {\n const id = await fieldUsageReportReceiptId(\n options.tenantId,\n options.userId,\n options.objectRef,\n options.fieldName,\n options.period,\n );\n if (await this.get(id)) return false;\n try {\n await this.create({ ...options, id, _insertOnly: true });\n return true;\n } catch (error) {\n // Only a receipt that now exists proves another request won the race;\n // validation/schema/connection failures must remain visible.\n if (await this.get(id)) return false;\n throw error;\n }\n }\n}\n\n/**\n * Deterministic bucket row id (the TenantUsageMetric `recordUsage` precedent):\n * SHA-256 over the natural key, formatted as a v5-style UUID so the id column\n * stays native UUID on PostgreSQL/DuckDB.\n */\nexport async function fieldUsageCounterId(\n tenantId: string,\n objectRef: string,\n fieldName: string,\n period: string,\n): Promise<string> {\n return deterministicFieldsUuid([\n 'field-usage-counter',\n tenantId,\n objectRef,\n fieldName,\n period,\n ]);\n}\n\n/** Deterministic receipt id for the durable once-per-day contribution rule. */\nexport async function fieldUsageReportReceiptId(\n tenantId: string,\n userId: string,\n objectRef: string,\n fieldName: string,\n period: string,\n): Promise<string> {\n return deterministicFieldsUuid([\n 'field-usage-report-receipt',\n tenantId,\n userId,\n objectRef,\n fieldName,\n period,\n ]);\n}\n\n/** Mirrors the resolver's policy-addressable exclusions, plus transient. */\nfunction isUsageAddressableField(field: RegisteredFieldInfo): boolean {\n if (field._meta?.__smrtSystemField === true) {\n return false;\n }\n if (\n field.type === 'oneToMany' ||\n field.type === 'manyToMany' ||\n field.type === 'meta'\n ) {\n return false;\n }\n return !isTransientField(field);\n}\n\n/**\n * Histogram eligibility (#2051 pin): ONLY low-cardinality field types —\n * `boolean` and reference ids (`foreignKey` / `crossPackageRef`). Free text\n * is NEVER histogrammed, even when non-sensitive (PII risk); numeric,\n * datetime, and json fields are count-only too.\n */\nexport function isHistogramEligibleField(field: RegisteredFieldInfo): boolean {\n return (\n field.type === 'boolean' ||\n field.type === 'foreignKey' ||\n field.type === 'crossPackageRef'\n );\n}\n\n/**\n * Serialize one sample into a histogram key, type-checked against the field:\n * booleans must be real booleans (`'true'` / `'false'` keys); reference ids\n * must be STORABLE ids for the field — `isStorableReferenceId` applies the same\n * native-UUID / `idType: 'text'` rule stored defaults are held to, within the\n * bounded key length. Anything else is skipped (the submission still counts —\n * count-only for that sample).\n *\n * The reference check is load-bearing, not cosmetic: recording an unstorable\n * id would let an authenticated caller poison a histogram, win dominance with\n * it, and produce a `default` suggestion whose write is then rejected by the\n * same rule — turning a bad sample into a stuck generation candidate.\n */\nexport function serializeHistogramSample(\n field: RegisteredFieldInfo,\n value: unknown,\n): string | null {\n if (field.type === 'boolean') {\n return typeof value === 'boolean' ? String(value) : null;\n }\n if (\n isStorableReferenceId(field, value) &&\n value.length <= MAX_VALUE_HISTOGRAM_KEY_LENGTH\n ) {\n return value;\n }\n return null;\n}\n\n/** Decode a histogram key back into the typed value it was recorded from. */\nexport function decodeHistogramKey(\n field: RegisteredFieldInfo,\n key: string,\n): unknown {\n if (field.type === 'boolean') {\n return key === 'true';\n }\n return key;\n}\n\nfunction validateEntriesInput(\n rawEntries: FieldUsageReportEntry[] | undefined,\n): FieldUsageReportEntry[] {\n if (!Array.isArray(rawEntries) || rawEntries.length === 0) {\n throw new Error(\n 'reportUsage requires a non-empty \"entries\" array of ' +\n '{ objectRef, fieldName, value?, matchedDefault? } samples',\n );\n }\n if (rawEntries.length > MAX_USAGE_REPORT_ENTRIES) {\n throw new Error(\n `reportUsage accepts at most ${MAX_USAGE_REPORT_ENTRIES} entries ` +\n `per call (got ${rawEntries.length})`,\n );\n }\n for (const entry of rawEntries) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof entry.objectRef !== 'string' ||\n entry.objectRef.trim() === '' ||\n typeof entry.fieldName !== 'string' ||\n entry.fieldName.trim() === ''\n ) {\n throw new Error(\n 'reportUsage entries must carry non-empty objectRef and fieldName strings',\n );\n }\n }\n return rawEntries;\n}\n","import { importWorkspaceModule } from '@happyvertical/smrt-core/utils/import-workspace-module';\nimport {\n assertTenantReadAllowed,\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { getCachedFieldPolicy, setCachedFieldPolicy } from './cache.js';\nimport { FieldPolicyCollection } from './collections/FieldPolicyCollection.js';\nimport {\n buildCodeSeedDelta,\n buildCodeSeedVisibility,\n type FieldDefinitionMap,\n getCodeSeedGroup,\n getObjectFieldMap,\n isPolicyAddressableField,\n isRequiredField,\n isUsableRequiredDefault,\n} from './field-definitions.js';\nimport type { FieldPolicy } from './models/FieldPolicy.js';\nimport type {\n ExplainedObjectFieldPolicy,\n FieldPolicyDelta,\n FieldPolicyLayerContribution,\n FieldPolicyTenantHierarchyProvider,\n FieldPolicyTenantNode,\n FieldPolicyUsersModule,\n FieldPolicyVisibility,\n ResolvedFieldPolicy,\n ResolvedObjectFieldPolicy,\n ResolveFieldPolicyOptions,\n SmrtClassOptions,\n} from './types.js';\n\n/** Accumulated merge state for one field while layers apply. */\ninterface MergedPolicyState {\n default?: { value: unknown };\n visibility: FieldPolicyVisibility;\n help?: string;\n label?: string;\n order?: number;\n locked?: boolean;\n}\n\n/**\n * Resolve the merged field policy for `objectRef` in the given\n * `(tenantId, userId)` context: code seed → app rows → tenant rows (hierarchy\n * walk root → leaf) → user rows. Defaults AND visibility both resolve through\n * the user tier.\n *\n * Results are cached per `(database, objectRef, tenantId, userId)` with a\n * short TTL; `FieldPolicy.save()`/`.delete()` invalidate the object's entries.\n */\nexport async function resolveFieldPolicy(\n objectRef: string,\n options: ResolveFieldPolicyOptions = {},\n): Promise<ResolvedObjectFieldPolicy> {\n const explained = await resolveFieldPolicyExplained(objectRef, options);\n return { objectRef: explained.objectRef, fields: explained.fields };\n}\n\n/**\n * The tenant ids whose rows participate in precedence for a tenant, root to\n * leaf. Catalog code uses this instead of reimplementing inheritance-break\n * handling when it decides whether an inherited override is customized.\n */\nexport async function resolveSurvivingTenantChainIds(\n tenantId: string,\n options: ResolveFieldPolicyOptions = {},\n): Promise<string[]> {\n assertResolutionAllowedInContext(tenantId, null);\n const chain = await resolveTenantChain(tenantId, options);\n return selectSurvivingChainSuffix(chain).map((node) => node.id);\n}\n\n/**\n * Explain variant: the merged result plus ordered per-layer contributions for\n * each field, so the gear UI (#2049, \"shows inherited base\") and the control\n * panel (#2050, effective-value-per-layer) never re-derive precedence.\n *\n * A user-layer row suppressed by an effective org lock is omitted from the\n * layer list too — the listed layers always reproduce the merged result.\n */\nexport async function resolveFieldPolicyExplained(\n objectRef: string,\n options: ResolveFieldPolicyOptions = {},\n): Promise<ExplainedObjectFieldPolicy> {\n const tenantId = options.tenantId ?? null;\n const userId = options.userId ?? null;\n\n assertResolutionAllowedInContext(tenantId, userId);\n\n let collection: FieldPolicyCollection | null = null;\n let cacheDb: unknown = options.db;\n if (options.db) {\n collection = await FieldPolicyCollection.create({ db: options.db });\n cacheDb = collection.db;\n }\n\n // The field map load also validates objectRef against the live registry, so\n // unknown refs throw before the cache is consulted.\n const fieldMap = await getObjectFieldMap(objectRef);\n\n const hasExcludedRows = (options.excludePolicyIds?.size ?? 0) > 0;\n if (!hasExcludedRows) {\n const cached = getCachedFieldPolicy(\n objectRef,\n tenantId,\n userId,\n cacheDb,\n options.tenantHierarchyLoader,\n );\n if (cached) {\n return cached;\n }\n }\n\n const policyFields = selectPolicyAddressableFields(fieldMap);\n const codeVisibility = buildCodeSeedVisibility(fieldMap);\n\n const appRows = collection\n ? await collection.getAppRows(objectRef)\n : new Map<string, FieldPolicy>();\n\n let survivingChain: FieldPolicyTenantNode[] = [];\n let tenantRows = new Map<string, Map<string, FieldPolicy>>();\n if (collection && tenantId) {\n const chain = await resolveTenantChain(tenantId, options);\n // Permission-inheritance breaks are chain-STRUCTURAL (node flags, not\n // rows), so a break at node i discards every earlier tenant contribution\n // for ALL fields — the merge baseline resets to the app-layer state\n // there. Only the suffix from the LAST break participates in merging and\n // in the explained layers, so sequentially replaying the listed deltas\n // always reproduces the merged result.\n survivingChain = selectSurvivingChainSuffix(chain);\n tenantRows = await collection.getTenantRows(\n objectRef,\n survivingChain.map((node) => node.id),\n );\n }\n\n const userRows =\n collection && userId\n ? await collection.getUserRows(objectRef, userId)\n : new Map<string, FieldPolicy>();\n\n const fields: Record<string, ResolvedFieldPolicy> = {};\n const layers: Record<string, FieldPolicyLayerContribution[]> = {};\n\n for (const [fieldName, fieldDef] of policyFields) {\n const contributions: FieldPolicyLayerContribution[] = [];\n\n const codeDelta = buildCodeSeedDelta(\n fieldDef,\n codeVisibility.get(fieldName) ?? 'basic',\n );\n contributions.push({ layer: 'code', delta: codeDelta });\n\n let state: MergedPolicyState = applyDelta(\n { visibility: 'basic' },\n codeDelta,\n );\n\n const appRow = appRows.get(fieldName);\n if (appRow && !options.excludePolicyIds?.has(String(appRow.id))) {\n const delta = rowToDelta(appRow);\n contributions.push({ layer: 'app', delta });\n state = applyDelta(state, delta);\n }\n\n // Tenant chain walk, root → leaf, over the surviving suffix only (nodes\n // before the last permission-inheritance break contribute nothing — see\n // selectSurvivingChainSuffix). Equivalent to smrt-features' baseline\n // walk, but the explained contributions never list discarded ancestors.\n for (const node of survivingChain) {\n const row = tenantRows.get(node.id)?.get(fieldName);\n if (row && !options.excludePolicyIds?.has(String(row.id))) {\n const delta = rowToDelta(row);\n contributions.push({ layer: 'tenant', tenantId: node.id, delta });\n state = applyDelta(state, delta);\n }\n }\n\n // Org lock: when the code/app/tenant tiers resolve locked, the user tier\n // is skipped entirely — a stale user row cannot bypass a later lock.\n const orgLocked = state.locked === true;\n const userRow = userId ? userRows.get(fieldName) : undefined;\n if (\n userRow &&\n !options.excludePolicyIds?.has(String(userRow.id)) &&\n !orgLocked\n ) {\n const delta = rowToDelta(userRow);\n contributions.push({ layer: 'user', userId: userId as string, delta });\n state = applyDelta(state, delta);\n }\n\n // Resolver-side required-field safety net: a required field with no\n // usable resolved default is ALWAYS visible, regardless of stored\n // visibility — write-time enforcement alone breaks when a DIFFERENT row's\n // deletion removes the default a demotion relied on.\n const required = isRequiredField(fieldDef);\n let visibilityForced = false;\n if (\n required &&\n !isUsableRequiredDefault(state.default) &&\n state.visibility !== 'basic'\n ) {\n state = { ...state, visibility: 'basic' };\n visibilityForced = true;\n }\n\n fields[fieldName] = {\n fieldName,\n hasDefault: state.default !== undefined,\n defaultValue: state.default?.value,\n visibility: state.visibility,\n help: state.help ?? null,\n label: state.label ?? null,\n order: state.order ?? null,\n group: getCodeSeedGroup(fieldDef),\n locked: state.locked === true,\n required,\n ...(visibilityForced ? { visibilityForced: true } : {}),\n };\n layers[fieldName] = contributions;\n }\n\n const explained: ExplainedObjectFieldPolicy = { objectRef, fields, layers };\n if (!hasExcludedRows) {\n setCachedFieldPolicy(\n objectRef,\n tenantId,\n userId,\n cacheDb,\n explained,\n options.tenantHierarchyLoader,\n );\n }\n return explained;\n}\n\n/**\n * Fail-closed isolation guard: an active non-bypass tenant context may only\n * resolve its own tenant and its own user. App-only resolution (`tenantId`\n * null) is always allowed — app rows are global data.\n *\n * Mirrors the write-side rule in `FieldPolicy`: a MISSING identity component\n * denies, it never skips. A context that carries permissions but no user id\n * (no `resolveUserId` hook configured — API-key auth, service principals,\n * background jobs) must not be able to read any user's resolved policy.\n * Context-LESS callers stay allowed: `resolveFieldPolicy` is a trusted\n * server-side API, and the public `resolveBatch` route never lets a request\n * body select a user — it takes identity from the ambient context alone.\n */\nfunction assertResolutionAllowedInContext(\n tenantId: string | null,\n userId: string | null,\n): void {\n if (tenantId) {\n assertTenantReadAllowed(tenantId, 'resolveFieldPolicy');\n }\n\n if (!userId) {\n return;\n }\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return;\n }\n if (context.userId === undefined) {\n throw new TenantIsolationError(\n `Tenant isolation violation in resolveFieldPolicy: the ambient ` +\n `context carries no user id, so user-scope resolution for ` +\n `'${userId}' is not attributable`,\n { tenantId: context.tenantId },\n );\n }\n if (context.userId !== userId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in resolveFieldPolicy: context user is ` +\n `'${context.userId}' but resolution requested '${userId}'`,\n { tenantId: context.tenantId },\n );\n }\n}\n\n/**\n * Fields that participate in policy resolution: everything except injected\n * framework system fields, relationship pseudo-fields, and STI meta\n * internals (matching the exclusions of the generated web field definitions).\n */\nfunction selectPolicyAddressableFields(\n fieldMap: FieldDefinitionMap,\n): FieldDefinitionMap {\n const selected: FieldDefinitionMap = new Map();\n for (const [name, field] of fieldMap) {\n if (isPolicyAddressableField(field)) {\n selected.set(name, field);\n }\n }\n return selected;\n}\n\n/** A stored row's sparse contribution (NULL columns contribute nothing). */\nfunction rowToDelta(row: FieldPolicy): FieldPolicyDelta {\n const delta: FieldPolicyDelta = {};\n\n if (row.defaultValue !== null && row.defaultValue !== undefined) {\n try {\n delta.default = { value: JSON.parse(row.defaultValue) };\n } catch {\n // Unparseable stored JSON (should be prevented by save-time validation)\n // contributes nothing rather than poisoning resolution.\n }\n }\n if (row.visibility !== null && row.visibility !== undefined) {\n delta.visibility = row.visibility;\n }\n if (row.help !== null && row.help !== undefined) {\n delta.help = row.help;\n }\n if (row.label !== null && row.label !== undefined) {\n delta.label = row.label;\n }\n if (row.displayOrder !== null && row.displayOrder !== undefined) {\n delta.order = row.displayOrder;\n }\n if (row.locked !== null && row.locked !== undefined) {\n delta.locked = row.locked;\n }\n\n return delta;\n}\n\nfunction applyDelta(\n state: MergedPolicyState,\n delta: FieldPolicyDelta,\n): MergedPolicyState {\n return {\n default: delta.default ?? state.default,\n visibility: delta.visibility ?? state.visibility,\n help: delta.help ?? state.help,\n label: delta.label ?? state.label,\n order: delta.order ?? state.order,\n locked: delta.locked ?? state.locked,\n };\n}\n\n/**\n * The chain suffix that actually participates in merging: nodes from the\n * LAST permission-inheritance break onward (a node breaks inheritance when\n * its parent does not cascade permissions or it does not accept them —\n * smrt-features semantics). Everything before the last break is discarded\n * for every field, so it is excluded from both merging and the explained\n * layer contributions.\n */\nfunction selectSurvivingChainSuffix(\n chain: FieldPolicyTenantNode[],\n): FieldPolicyTenantNode[] {\n let survivingStart = 0;\n for (let index = 1; index < chain.length; index++) {\n const inherits =\n chain[index - 1].cascadePermissions && chain[index].inheritPermissions;\n if (!inherits) {\n survivingStart = index;\n }\n }\n return chain.slice(survivingStart);\n}\n\nasync function resolveTenantChain(\n tenantId: string,\n options: ResolveFieldPolicyOptions,\n): Promise<FieldPolicyTenantNode[]> {\n const loader = options.tenantHierarchyLoader || defaultTenantHierarchyLoader;\n const provider = await loader({ db: options.db } as SmrtClassOptions);\n\n if (provider) {\n const chain = await provider.getChain(tenantId);\n if (chain.length > 0) {\n return chain;\n }\n }\n\n // Flat-tenant fallback (no hierarchy provider, or the provider does not\n // know the tenant): treat the tenant as a single-node chain.\n return [{ id: tenantId, inheritPermissions: true, cascadePermissions: true }];\n}\n\n/**\n * Default hierarchy loader: dynamic-imports `@happyvertical/smrt-users` (the\n * smrt-features precedent — a loader function, not a container registration)\n * and returns `null` when it is not installed so resolution degrades to the\n * flat-tenant fallback.\n */\nasync function defaultTenantHierarchyLoader(\n options: SmrtClassOptions,\n): Promise<FieldPolicyTenantHierarchyProvider | null> {\n try {\n const usersModule = await importWorkspaceModule<FieldPolicyUsersModule>({\n packageName: '@happyvertical/smrt-users',\n sourceEntry: 'packages/users/src/collections/index.ts',\n purpose: 'tenant-aware field policy resolution',\n });\n\n const tenantCollection = await usersModule.TenantCollection.create(options);\n return {\n async getChain(tenantId: string): Promise<FieldPolicyTenantNode[]> {\n const tenant = await tenantCollection.get({ id: tenantId });\n if (!tenant) {\n return [];\n }\n\n const ancestors = await tenantCollection.getAncestorsFromRoot(tenantId);\n return [...ancestors, tenant].map((node) => ({\n id: String(node.id),\n inheritPermissions: Boolean(node.inheritPermissions),\n cascadePermissions: Boolean(node.cascadePermissions),\n }));\n },\n };\n } catch (error) {\n if (isMissingUsersDependency(error)) {\n return null;\n }\n throw error;\n }\n}\n\n/** Node's missing-module message shapes, capturing the quoted specifier. */\nconst MISSING_MODULE_TARGET_PATTERN =\n /Cannot find (?:package|module) '([^']+)'/;\n\n/** Whether a missing-module TARGET specifier is smrt-users (or a subpath). */\nfunction isUsersSpecifier(target: string): boolean {\n return (\n target === '@happyvertical/smrt-users' ||\n target.startsWith('@happyvertical/smrt-users/')\n );\n}\n\n/**\n * Whether an import failure means `@happyvertical/smrt-users` is simply not\n * installed (→ flat-tenant fallback) rather than installed-but-broken\n * (→ rethrow, surfacing the problem instead of silently losing ancestor\n * locks/defaults).\n *\n * The decision is made on the missing-module TARGET parsed from Node's\n * `Cannot find package/module '<specifier>'` message (walking the full\n * `cause` chain): only a target that IS smrt-users (or one of its subpaths)\n * counts. A transitive failure INSIDE an installed smrt-users names the\n * other package as the target — with the users path merely appearing as the\n * importer — and therefore rethrows. `importWorkspaceModule`'s own\n * source-fallback wrapper (\"Failed to load @happyvertical/smrt-users for\n * ...\") is also accepted: it is thrown only when the users package itself\n * cannot be located.\n *\n * Exported for direct testing; not re-exported from the package index.\n */\nexport function isMissingUsersDependency(error: unknown): boolean {\n let current: unknown = error;\n const seen = new Set<unknown>();\n\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current);\n\n const match = current.message.match(MISSING_MODULE_TARGET_PATTERN);\n if (match && isUsersSpecifier(match[1])) {\n return true;\n }\n\n if (\n current.message.includes('Failed to load @happyvertical/smrt-users for')\n ) {\n return true;\n }\n\n current = current.cause;\n }\n\n return false;\n}\n","/**\n * Server-side data builder for the field-policy AdminShell destination.\n *\n * This deliberately mirrors the `@happyvertical/smrt-svelte/settings`\n * contract structurally. Fields does not depend on smrt-svelte, and only the\n * selected object carries its browser-safe field definitions.\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport {\n FieldPolicyCollection,\n MAX_FIELD_POLICY_AUDIT_OBJECT_REFS,\n} from './collections/FieldPolicyCollection.js';\nimport {\n getFieldReadPermission,\n getObjectFieldMap,\n isPolicyAddressableField,\n isSensitiveField,\n isTransientField,\n} from './field-definitions.js';\nimport type { FieldPolicyAuditSnapshot } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 100;\nconst FORM_TYPES = new Set([\n 'text',\n 'integer',\n 'decimal',\n 'boolean',\n 'datetime',\n 'json',\n 'foreignKey',\n 'crossPackageRef',\n]);\nconst SYSTEM_NAMES = new Set([\n 'id',\n 'slug',\n 'context',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n 'deletedAt',\n 'deleted_at',\n 'tenantId',\n 'tenant_id',\n]);\n\nexport interface FieldPolicyCatalogField {\n type:\n | 'text'\n | 'integer'\n | 'decimal'\n | 'boolean'\n | 'datetime'\n | 'json'\n | 'foreignKey'\n | 'crossPackageRef';\n required?: boolean;\n default?: unknown;\n description?: string;\n ui?: { basic?: boolean; group?: string; order?: number; locked?: boolean };\n}\n\nexport interface FieldPolicySummaryItem {\n id: string;\n label: string;\n description?: string;\n eyebrow?: string;\n status?: string;\n objectRef: string;\n fieldName: string;\n className: string;\n packageName: string;\n}\n\nexport interface FieldPolicyDetailItem extends FieldPolicySummaryItem {\n fields: Record<string, FieldPolicyCatalogField>;\n}\n\n/** Structural SettingsCatalogPage mirror; `SettingsCatalog` accepts it directly. */\nexport interface FieldPolicySettingsCatalogPage {\n items: FieldPolicySummaryItem[];\n selected: FieldPolicyDetailItem | null;\n query: string;\n page: number;\n pageSize: number;\n total: number;\n}\n\nexport interface FieldPolicyCatalogObjectSummary {\n objectRef: string;\n className: string;\n packageName: string;\n fieldCount: number;\n}\n\nexport interface FieldPolicySettingsCatalogData {\n page: FieldPolicySettingsCatalogPage;\n audit: FieldPolicyAuditSnapshot;\n objects: FieldPolicyCatalogObjectSummary[];\n packages: string[];\n filters: {\n packageFilter: string | null;\n objectFilter: string | null;\n customizedOnly: boolean;\n };\n}\n\nexport interface FieldPolicySettingsCatalogQuery {\n query?: string | null;\n page?: number | null;\n pageSize?: number | null;\n selectedId?: string | null;\n packageFilter?: string | null;\n objectFilter?: string | null;\n customizedOnly?: boolean;\n}\n\nexport interface BuildFieldPolicySettingsCatalogOptions\n extends FieldPolicySettingsCatalogQuery {\n db?: SmrtClassOptions['db'];\n collection?: FieldPolicyCollection;\n objectRefs?: string[];\n}\n\ninterface CatalogObject {\n objectRef: string;\n className: string;\n packageName: string;\n fields: Record<string, FieldPolicyCatalogField>;\n}\n\nexport function fieldPolicyCatalogItemId(\n objectRef: string,\n fieldName: string,\n): string {\n return `${objectRef}::${fieldName}`;\n}\n\nexport function parseFieldPolicyCatalogQuery(\n params: URLSearchParams,\n): FieldPolicySettingsCatalogQuery {\n return {\n query: params.get('q'),\n page: integerParam(params.get('page')),\n pageSize: integerParam(params.get('pageSize')),\n selectedId: params.get('selected'),\n packageFilter: params.get('package'),\n objectFilter: params.get('object'),\n customizedOnly: params.get('customized') === '1',\n };\n}\n\nexport async function buildFieldPolicySettingsCatalog(\n options: BuildFieldPolicySettingsCatalogOptions,\n): Promise<FieldPolicySettingsCatalogData> {\n const collection =\n options.collection ??\n (options.db\n ? await FieldPolicyCollection.create({ db: options.db })\n : null);\n if (!collection) throw new Error('A db or FieldPolicyCollection is required');\n\n const filters = {\n packageFilter: stringFilter(options.packageFilter),\n objectFilter: stringFilter(options.objectFilter),\n customizedOnly: options.customizedOnly === true,\n };\n const query = options.query?.trim() ?? '';\n const pageSize = clamp(options.pageSize, 1, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);\n // Authorization is established before registry enumeration, but this must\n // stay a capability-only call: no policy rows are read until the URL-driven\n // page (or the explicit customized filter) identifies its object refs.\n const baseAudit = await collection.policyAudit({ summaryOnly: true });\n if (!baseAudit.caller.canManageOrg) {\n return {\n page: emptyPage(query, pageSize),\n audit: baseAudit,\n objects: [],\n packages: [],\n filters,\n };\n }\n\n const objects = await listCatalogObjects(options.objectRefs);\n const candidates = objects.filter(\n (object) =>\n (!filters.packageFilter ||\n object.packageName === filters.packageFilter) &&\n (!filters.objectFilter || object.objectRef === filters.objectFilter),\n );\n const allCandidateRefs = candidates.map((object) => object.objectRef);\n const countAudit =\n filters.customizedOnly && allCandidateRefs.length\n ? await loadAuditCounts(collection, allCandidateRefs, baseAudit)\n : baseAudit;\n const customized = customizedKeys(countAudit);\n const entries = candidates.flatMap((object) =>\n Object.entries(object.fields)\n .filter(\n ([fieldName]) =>\n !filters.customizedOnly ||\n customized.has(fieldPolicyCatalogItemId(object.objectRef, fieldName)),\n )\n .map(([fieldName, field]) => ({\n item: {\n id: fieldPolicyCatalogItemId(object.objectRef, fieldName),\n label: fieldName,\n ...(field.description ? { description: field.description } : {}),\n eyebrow: `${object.className} · ${object.packageName}`,\n objectRef: object.objectRef,\n fieldName,\n className: object.className,\n packageName: object.packageName,\n },\n search:\n `${fieldName} ${spaced(fieldName)} ${object.className} ${object.packageName} ${object.objectRef} ${field.description ?? ''}`.toLowerCase(),\n })),\n );\n const filtered = query\n ? entries.filter((entry) => entry.search.includes(query.toLowerCase()))\n : entries;\n const total = filtered.length;\n const page = clamp(\n options.page,\n 1,\n Math.max(1, Math.ceil(total / pageSize)),\n 1,\n );\n const items = filtered\n .slice((page - 1) * pageSize, page * pageSize)\n .map((entry) => entry.item);\n const selectedSummary =\n (options.selectedId\n ? filtered.find((entry) => entry.item.id === options.selectedId)?.item\n : undefined) ?? items[0];\n const selected = selectedSummary\n ? {\n ...selectedSummary,\n fields:\n objects.find(\n (object) => object.objectRef === selectedSummary.objectRef,\n )?.fields ?? {},\n }\n : null;\n const auditRefs = selected\n ? uniqueRefs([selected.objectRef, ...items.map((item) => item.objectRef)])\n : [];\n const audit = auditRefs.length\n ? await collection.policyAudit({\n objectRefs: auditRefs.slice(0, MAX_FIELD_POLICY_AUDIT_OBJECT_REFS),\n countObjectRefs: auditRefs,\n includeDrift: true,\n })\n : await collection.policyAudit({ includeDrift: true });\n return {\n page: { items, selected, query, page, pageSize, total },\n audit,\n objects: objects.map(({ objectRef, className, packageName, fields }) => ({\n objectRef,\n className,\n packageName,\n fieldCount: Object.keys(fields).length,\n })),\n packages: [...new Set(objects.map((object) => object.packageName))].sort(),\n filters,\n };\n}\n\nasync function listCatalogObjects(\n objectRefs?: string[],\n): Promise<CatalogObject[]> {\n const refs: string[] = objectRefs\n ? [...objectRefs]\n : Array.from(ObjectRegistry.getPublicClasses().values()).reduce<string[]>(\n (result, registered) => {\n if (registered.qualifiedName) result.push(registered.qualifiedName);\n return result;\n },\n [],\n );\n const unique = [...new Set(refs)].sort();\n const objects: CatalogObject[] = [];\n for (const objectRef of unique) {\n const registered = ObjectRegistry.getClassByQualifiedName(objectRef);\n if (\n !registered ||\n ObjectRegistry.getTableName(objectRef)?.startsWith('_smrt_')\n )\n continue;\n const fields = await getObjectFieldMap(objectRef);\n const selected: Record<string, FieldPolicyCatalogField> = {};\n for (const [name, definition] of fields) {\n if (\n SYSTEM_NAMES.has(name) ||\n !isPolicyAddressableField(definition) ||\n isSensitiveField(definition) ||\n isTransientField(definition) ||\n getFieldReadPermission(definition) !== undefined ||\n !FORM_TYPES.has(String(definition.type))\n )\n continue;\n selected[name] = {\n type: definition.type as FieldPolicyCatalogField['type'],\n ...(definition.required === true ? { required: true } : {}),\n ...(definition.default !== undefined\n ? { default: definition.default }\n : {}),\n ...(typeof definition.description === 'string'\n ? { description: definition.description }\n : {}),\n };\n }\n if (!Object.keys(selected).length) continue;\n const colon = objectRef.lastIndexOf(':');\n const packageName = colon === -1 ? '' : objectRef.slice(0, colon);\n const className = colon === -1 ? objectRef : objectRef.slice(colon + 1);\n objects.push({ objectRef, className, packageName, fields: selected });\n }\n return objects;\n}\n\nasync function loadAuditCounts(\n collection: FieldPolicyCollection,\n refs: string[],\n baseline: FieldPolicyAuditSnapshot,\n): Promise<FieldPolicyAuditSnapshot> {\n const userOverrideCounts: FieldPolicyAuditSnapshot['userOverrideCounts'] = {};\n const orgRows: FieldPolicyAuditSnapshot['orgRows'] = [];\n const appRows: FieldPolicyAuditSnapshot['appRows'] = [];\n const inheritedOrgKeys: FieldPolicyAuditSnapshot['inheritedOrgKeys'] = {};\n const chunkSize = MAX_FIELD_POLICY_AUDIT_OBJECT_REFS;\n for (let offset = 0; offset < refs.length; offset += chunkSize) {\n const audit = await collection.policyAudit({\n objectRefs: refs.slice(offset, offset + chunkSize),\n countObjectRefs: refs.slice(offset, offset + chunkSize),\n countsOnly: true,\n });\n for (const [objectRef, byField] of Object.entries(\n audit.userOverrideCounts,\n )) {\n userOverrideCounts[objectRef] = {\n ...(userOverrideCounts[objectRef] ?? {}),\n ...byField,\n };\n }\n orgRows.push(...audit.orgRows);\n appRows.push(...audit.appRows);\n for (const [objectRef, fieldNames] of Object.entries(\n audit.inheritedOrgKeys,\n )) {\n const names = inheritedOrgKeys[objectRef] ?? [];\n inheritedOrgKeys[objectRef] = names;\n for (const fieldName of fieldNames) {\n if (!names.includes(fieldName)) names.push(fieldName);\n }\n }\n }\n return {\n ...baseline,\n orgRows,\n appRows,\n inheritedOrgKeys,\n userOverrideCounts,\n };\n}\n\nfunction customizedKeys(audit: FieldPolicyAuditSnapshot): Set<string> {\n const keys = new Set<string>();\n for (const row of [...audit.orgRows, ...audit.appRows])\n keys.add(fieldPolicyCatalogItemId(row.objectRef, row.fieldName));\n for (const [objectRef, names] of Object.entries(audit.inheritedOrgKeys))\n for (const name of names)\n keys.add(fieldPolicyCatalogItemId(objectRef, name));\n for (const [objectRef, fields] of Object.entries(audit.userOverrideCounts))\n for (const [name, count] of Object.entries(fields))\n if (count > 0) keys.add(fieldPolicyCatalogItemId(objectRef, name));\n return keys;\n}\n\nfunction emptyPage(\n query: string,\n pageSize: number,\n): FieldPolicySettingsCatalogPage {\n return { items: [], selected: null, query, page: 1, pageSize, total: 0 };\n}\nfunction uniqueRefs(refs: string[]): string[] {\n return [...new Set(refs)];\n}\nfunction stringFilter(value: string | null | undefined): string | null {\n const trimmed = value?.trim();\n return trimmed ? trimmed : null;\n}\nfunction integerParam(value: string | null): number | null {\n return value && /^\\d+$/.test(value) ? Number(value) : null;\n}\nfunction clamp(\n value: number | null | undefined,\n min: number,\n max: number,\n fallback: number,\n): number {\n return Number.isFinite(value)\n ? Math.min(max, Math.max(min, Math.trunc(value as number)))\n : fallback;\n}\nfunction spaced(value: string): string {\n return value.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]/g, ' ');\n}\n","/**\n * The #2051 learning loop: scheduled aggregation/retention over\n * `_smrt_field_usage_counters` and threshold-driven generation of\n * `_smrt_field_policy_suggestions`.\n *\n * Scheduling substrate: there is NO generic cron package API — the de-facto\n * convention is an `AgentSchedule` row (`@happyvertical/smrt-agents`)\n * dispatched by smrt-jobs' `ScheduleRunner`, which resolves `agentType`\n * through the `ObjectRegistry` and invokes the method on the registered\n * class. {@link FieldUsageLearningAgent} is that schedule target. It is a\n * registered `@smrt()` SmrtObject — deliberately NOT a subclass of the agents\n * package's `Agent` base: smrt-agents sits above smrt-fields in the\n * dependency DAG (it hard-depends on smrt-users/ai/secrets, all of which this\n * package keeps optional), and the dispatch machinery only requires registry\n * resolution plus the jobs package's `backgroundEligibleMethods` allowlist\n * contract (a static property, no import needed). Schedule rows are created\n * DORMANT by `ensureFieldUsageLearningSchedules` (see `usage-schedules.ts`).\n *\n * Both jobs are tenant-safe by construction: in trusted execution (no ambient\n * tenant context, or super-admin bypass) they operate across all tenants;\n * inside a non-bypass tenant context they restrict themselves to the ambient\n * tenant (fail closed), so per-tenant schedule rows are also valid.\n */\n\nimport {\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { FieldPolicySuggestionCollection } from './collections/FieldPolicySuggestionCollection.js';\nimport {\n decodeHistogramKey,\n FieldUsageCounterCollection,\n isHistogramEligibleField,\n} from './collections/FieldUsageCounterCollection.js';\nimport {\n type FieldDefinitionMap,\n getFieldReadPermission,\n getObjectFieldMap,\n isSensitiveField,\n isTransientField,\n} from './field-definitions.js';\nimport {\n ACTIVE_SUGGESTION_KEY,\n type FieldPolicySuggestion,\n} from './models/FieldPolicySuggestion.js';\nimport type { FieldUsageCounter } from './models/FieldUsageCounter.js';\nimport {\n emptyHistogram,\n fieldUsagePeriodForDate,\n} from './models/FieldUsageCounter.js';\nimport type {\n FieldPolicySuggestionKind,\n ResolvedFieldPolicy,\n} from './types.js';\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\n// ---------------------------------------------------------------------------\n// Config (documented defaults; overridable per run through the schedule's\n// `methodArgs` payload)\n// ---------------------------------------------------------------------------\n\nexport interface FieldUsageMaintenanceConfig {\n /** Drop counter buckets older than this (by `period`). Default 90 days. */\n counterMaxAgeMs: number;\n /** Keep at most this many counter rows (oldest pruned first). Default 100k. */\n counterMaxRows: number;\n /** Drop ACCEPTED suggestions decided longer ago than this. Default 180 days. */\n suggestionAcceptedMaxAgeMs: number;\n}\n\nexport const FIELD_USAGE_MAINTENANCE_DEFAULTS: FieldUsageMaintenanceConfig = {\n counterMaxAgeMs: 90 * DAY_MS,\n counterMaxRows: 100_000,\n suggestionAcceptedMaxAgeMs: 180 * DAY_MS,\n};\n\nexport interface FieldUsageSuggestionConfig {\n /** Usage window the thresholds evaluate over. Default 30 days. */\n windowDays: number;\n /** Distinct users required for a `promote` suggestion. Default 5. */\n minDistinctUsers: number;\n /** Submissions required before a `default` suggestion. Default 10. */\n minSetCount: number;\n /**\n * Share of windowed submissions a single value must reach for a `default`\n * suggestion (denominator is TOTAL setCount, so histogram overflow can only\n * make this more conservative). Default 0.8.\n */\n defaultDominanceRatio: number;\n}\n\nexport const FIELD_USAGE_SUGGESTION_DEFAULTS: FieldUsageSuggestionConfig = {\n windowDays: 30,\n minDistinctUsers: 5,\n minSetCount: 10,\n defaultDominanceRatio: 0.8,\n};\n\nexport interface FieldUsageMaintenanceSummary {\n countersPruned: number;\n receiptsPruned: number;\n suggestionsPruned: number;\n}\n\nexport interface FieldUsageSuggestionRunSummary {\n /**\n * Suggestions this run wrote. Under overlapping runs both may report a\n * create for the same candidate while the model's active-slot unique index\n * converges them onto ONE row (see `FieldPolicySuggestion.activeKey`) — the\n * tally is per-run work, not a row count.\n */\n created: number;\n /** Candidates suppressed by a pending or cooling-down suggestion. */\n suppressed: number;\n /** Distinct (tenant, objectRef, fieldName) groups evaluated. */\n groupsConsidered: number;\n /**\n * Groups whose evaluation threw and was skipped. A failing group never\n * aborts the run — a global pass must keep serving every other tenant.\n */\n groupsFailed: number;\n /**\n * Bounded sample of failure messages (`MAX_REPORTED_GROUP_FAILURES`) so an\n * operator can see WHY without the summary growing with the queue.\n */\n failures: string[];\n}\n\n/** Cap on {@link FieldUsageSuggestionRunSummary.failures} entries. */\nexport const MAX_REPORTED_GROUP_FAILURES = 5;\n\n// ---------------------------------------------------------------------------\n// Retention (the pruneChangeFeed shape: {maxAgeMs?, maxRows?}, oldest-first)\n// ---------------------------------------------------------------------------\n\nexport interface FieldUsageCounterRetention {\n maxAgeMs?: number;\n maxRows?: number;\n /** Restrict pruning to one tenant (ambient-context runs). */\n tenantId?: string | null;\n}\n\n/**\n * Prune counter rows to bound growth. Applies whichever bounds are provided\n * (at least one required): `maxAgeMs` drops buckets whose `period` day is\n * older than the cutoff; `maxRows` keeps only the newest N rows by\n * `(period, id)`, deleting oldest-first. Mirrors core's `pruneChangeFeed`.\n */\nexport async function pruneFieldUsageCounters(\n db: DatabaseInterface,\n retention: FieldUsageCounterRetention,\n): Promise<{ pruned: number }> {\n const { maxAgeMs, maxRows } = retention;\n if (maxAgeMs == null && maxRows == null) {\n throw new Error('pruneFieldUsageCounters requires maxAgeMs and/or maxRows');\n }\n if (maxAgeMs != null && (!Number.isFinite(maxAgeMs) || maxAgeMs < 0)) {\n throw new Error(\n `pruneFieldUsageCounters maxAgeMs must be >= 0, got ${maxAgeMs}`,\n );\n }\n if (maxRows != null && (!Number.isFinite(maxRows) || maxRows < 0)) {\n throw new Error(\n `pruneFieldUsageCounters maxRows must be >= 0, got ${maxRows}`,\n );\n }\n\n const tenantCondition = retention.tenantId ? ' AND tenant_id = ?' : '';\n const tenantParams = retention.tenantId ? [retention.tenantId] : [];\n let pruned = 0;\n\n if (maxAgeMs != null) {\n const cutoffDay = fieldUsagePeriodForDate(new Date(Date.now() - maxAgeMs));\n pruned += await deleteCounted(db, `period < ?${tenantCondition}`, [\n cutoffDay,\n ...tenantParams,\n ]);\n }\n\n if (maxRows != null) {\n const countRows = getQueryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM _smrt_field_usage_counters` +\n `${retention.tenantId ? ' WHERE tenant_id = ?' : ''}`,\n ...tenantParams,\n ),\n );\n const total = numberFromRow(countRows[0] ?? {}, 'total');\n const excess = total - Math.floor(maxRows);\n if (excess > 0) {\n await db.query(\n `DELETE FROM _smrt_field_usage_counters\n WHERE id IN (\n SELECT id FROM _smrt_field_usage_counters\n ${retention.tenantId ? 'WHERE tenant_id = ?' : ''}\n ORDER BY period ASC, id ASC\n LIMIT ${excess}\n )`,\n ...tenantParams,\n );\n pruned += excess;\n }\n }\n\n return { pruned };\n}\n\n/**\n * Drop durable anti-inflation receipts at the same age cutoff as counters.\n *\n * Deliberately do NOT delete a receipt merely because its counter row is\n * absent: report ingestion claims its receipt before merging the counter, so\n * an orphan sweep could otherwise reopen the once-per-user/day quota during\n * that in-flight interval. A max-row counter trim likewise leaves its recent\n * receipts until the age cutoff — retaining a de-duplication guard is safer\n * than allowing a second contribution for that day.\n */\nexport async function pruneFieldUsageReportReceipts(\n db: DatabaseInterface,\n options: { maxAgeMs: number; tenantId?: string | null },\n): Promise<{ pruned: number }> {\n if (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0) {\n throw new Error(\n `pruneFieldUsageReportReceipts maxAgeMs must be >= 0, got ` +\n `${options.maxAgeMs}`,\n );\n }\n const cutoffDay = fieldUsagePeriodForDate(\n new Date(Date.now() - options.maxAgeMs),\n );\n const tenantCondition = options.tenantId ? ' AND tenant_id = ?' : '';\n const tenantParams = options.tenantId ? [options.tenantId] : [];\n return {\n pruned: await deleteCounted(\n db,\n `period < ?${tenantCondition}`,\n [cutoffDay, ...tenantParams],\n '_smrt_field_usage_report_receipts',\n ),\n };\n}\n\nexport interface FieldPolicySuggestionRetention {\n /** Drop ACCEPTED suggestions decided longer ago than this. */\n acceptedMaxAgeMs: number;\n /** Restrict pruning to one tenant (ambient-context runs). */\n tenantId?: string | null;\n /** Clock override for deterministic tests. */\n now?: Date;\n}\n\n/**\n * Prune settled suggestion rows ONLY (#2051 pin): a dismissed suggestion once\n * its cool-down has fully elapsed (its suppression job is done), and an\n * accepted suggestion once it is old. Pending suggestions are NEVER pruned.\n */\nexport async function pruneFieldPolicySuggestions(\n db: DatabaseInterface,\n retention: FieldPolicySuggestionRetention,\n): Promise<{ pruned: number }> {\n const { acceptedMaxAgeMs } = retention;\n if (!Number.isFinite(acceptedMaxAgeMs) || acceptedMaxAgeMs < 0) {\n throw new Error(\n `pruneFieldPolicySuggestions acceptedMaxAgeMs must be >= 0, got ` +\n `${acceptedMaxAgeMs}`,\n );\n }\n const now = retention.now ?? new Date();\n const acceptedCutoff = new Date(now.getTime() - acceptedMaxAgeMs);\n const tenantCondition = retention.tenantId ? ' AND tenant_id = ?' : '';\n const tenantParams = retention.tenantId ? [retention.tenantId] : [];\n\n const pruned = await deleteCounted(\n db,\n `((status = 'dismissed' AND cooldown_until IS NOT NULL ` +\n `AND cooldown_until <= ?) ` +\n `OR (status = 'accepted' AND decided_at IS NOT NULL ` +\n `AND decided_at <= ?))${tenantCondition}`,\n [now.toISOString(), acceptedCutoff.toISOString(), ...tenantParams],\n '_smrt_field_policy_suggestions',\n );\n return { pruned };\n}\n\n// ---------------------------------------------------------------------------\n// Job entry points (pure functions; the agent methods delegate here)\n// ---------------------------------------------------------------------------\n\nexport interface RunFieldUsageMaintenanceOptions\n extends Partial<FieldUsageMaintenanceConfig> {\n db: DatabaseInterface;\n}\n\n/**\n * The \"aggregation\" schedule's work. Ingestion pre-aggregates into period\n * buckets, so the roll-up job's real job is retention: prune counter buckets\n * and durable receipts at the shared age cutoff, then settle old suggestion\n * rows. Max-row trimming leaves recent receipts intact to preserve daily\n * de-duplication while an ingestion merge is in flight.\n */\nexport async function runFieldUsageMaintenance(\n options: RunFieldUsageMaintenanceOptions,\n): Promise<FieldUsageMaintenanceSummary> {\n const config = normalizeMaintenanceConfig(options);\n const ambientTenantId = restrictingTenantId();\n\n const counters = await pruneFieldUsageCounters(options.db, {\n maxAgeMs: config.counterMaxAgeMs,\n maxRows: config.counterMaxRows,\n tenantId: ambientTenantId,\n });\n const receipts = await pruneFieldUsageReportReceipts(options.db, {\n maxAgeMs: config.counterMaxAgeMs,\n tenantId: ambientTenantId,\n });\n const suggestions = await pruneFieldPolicySuggestions(options.db, {\n acceptedMaxAgeMs: config.suggestionAcceptedMaxAgeMs,\n tenantId: ambientTenantId,\n });\n\n return {\n countersPruned: counters.pruned,\n receiptsPruned: receipts.pruned,\n suggestionsPruned: suggestions.pruned,\n };\n}\n\nexport interface RunFieldPolicySuggestionGenerationOptions\n extends Partial<FieldUsageSuggestionConfig> {\n db: DatabaseInterface;\n /** Clock override for deterministic tests. */\n now?: Date;\n}\n\n/**\n * The threshold job: evaluate windowed counters per\n * `(tenantId, objectRef, fieldName)` and create PENDING suggestions with\n * human-readable evidence.\n *\n * - `promote`: at least `minDistinctUsers` distinct users set the field to a\n * non-default value in the window AND the org-resolved visibility is not\n * already `basic`.\n * - `default`: at least `minSetCount` TOTAL submissions, a single recorded\n * value covers `defaultDominanceRatio` of those TOTAL submissions (not of\n * the deviations — see `FieldUsageCounter.submissionCount`), and it differs\n * from the org-resolved default. Only histogram-eligible fields\n * (low-cardinality, non-sensitive, non-gated) can ever qualify, and a group\n * containing a legacy bucket with no recorded total is skipped rather than\n * ratioed against the wrong denominator.\n *\n * Dedup: a candidate is suppressed while the same\n * `(tenantId, objectRef, fieldName, kind)` has a PENDING suggestion or a\n * DISMISSED one still inside its cool-down. Accepted history never blocks —\n * once accepted, the resolved policy itself stops regeneration (visibility is\n * basic / the default matches). The pre-check is an optimization only: the\n * single-active guarantee is STRUCTURAL (`FieldPolicySuggestion.activeKey` in\n * `conflictColumns`), so overlapping runs upsert onto one row instead of\n * duplicating.\n *\n * Sensitive, read-permission-gated, and transient fields are skipped\n * entirely: their usage rows are count-only observability data and never\n * produce suggestions.\n */\nexport async function runFieldPolicySuggestionGeneration(\n options: RunFieldPolicySuggestionGenerationOptions,\n): Promise<FieldUsageSuggestionRunSummary> {\n const config = normalizeSuggestionConfig(options);\n const now = options.now ?? new Date();\n const ambientTenantId = restrictingTenantId();\n\n const counters = await FieldUsageCounterCollection.create({\n db: options.db,\n });\n const suggestions = await FieldPolicySuggestionCollection.create({\n db: options.db,\n });\n\n const fromPeriod = fieldUsagePeriodForDate(\n new Date(now.getTime() - (config.windowDays - 1) * DAY_MS),\n );\n const toPeriod = fieldUsagePeriodForDate(now);\n const rows = await counters.listWindow({\n fromPeriod,\n toPeriod,\n tenantId: ambientTenantId,\n });\n\n const groups = groupCounters(rows);\n const fieldMaps = new Map<string, FieldDefinitionMap | null>();\n const resolvedPolicies = new Map<\n string,\n Record<string, ResolvedFieldPolicy>\n >();\n\n const summary: FieldUsageSuggestionRunSummary = {\n created: 0,\n suppressed: 0,\n groupsConsidered: 0,\n groupsFailed: 0,\n failures: [],\n };\n\n for (const group of groups) {\n summary.groupsConsidered += 1;\n\n // One bad group must NEVER abort the run: a global (cross-tenant) pass\n // processes every tenant, so an unprocessable group — a since-changed\n // field, a rejected proposal payload, a transient db error — would\n // otherwise starve every group after it for as long as its buckets live.\n // Failures are counted (with a bounded sample of messages) and the run\n // continues.\n try {\n await evaluateGroup(group);\n } catch (error) {\n summary.groupsFailed += 1;\n if (summary.failures.length < MAX_REPORTED_GROUP_FAILURES) {\n summary.failures.push(\n `${group.tenantId} ${group.objectRef}.${group.fieldName}: ` +\n `${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n\n return summary;\n\n async function evaluateGroup(group: CounterGroup): Promise<void> {\n let fieldMap = fieldMaps.get(group.objectRef);\n if (fieldMap === undefined) {\n try {\n fieldMap = await getObjectFieldMap(group.objectRef);\n } catch {\n fieldMap = null; // stale counters for a since-removed class\n }\n fieldMaps.set(group.objectRef, fieldMap);\n }\n if (!fieldMap) {\n return;\n }\n const fieldDef = fieldMap.get(group.fieldName);\n if (\n !fieldDef ||\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined ||\n isTransientField(fieldDef)\n ) {\n return;\n }\n\n const stats = mergeGroupStats(group.buckets);\n\n const policyKey = `${group.tenantId}\\0${group.objectRef}`;\n let orgFields = resolvedPolicies.get(policyKey);\n if (!orgFields) {\n // Org-tier resolution (code → app → tenant chain; no user tier). The\n // resolver's own context assertion keeps ambient-context runs honest.\n const { resolveFieldPolicy } = await import('./field-policy-resolver.js');\n const resolved = await resolveFieldPolicy(group.objectRef, {\n tenantId: group.tenantId,\n db: options.db,\n });\n orgFields = resolved.fields;\n resolvedPolicies.set(policyKey, orgFields);\n }\n const fieldPolicy = orgFields[group.fieldName];\n if (!fieldPolicy) {\n return;\n }\n\n // -- promote ---------------------------------------------------------\n if (\n stats.distinctUsers >= config.minDistinctUsers &&\n fieldPolicy.visibility !== 'basic'\n ) {\n const created = await createUnlessSuppressed(suggestions, {\n tenantId: group.tenantId,\n objectRef: group.objectRef,\n fieldName: group.fieldName,\n kind: 'promote',\n proposedValue: null,\n evidence: buildFieldUsageEvidence({\n kind: 'promote',\n fieldName: group.fieldName,\n objectRef: group.objectRef,\n windowStart: fromPeriod,\n windowEnd: toPeriod,\n distinctUsers: stats.distinctUsers,\n distinctUsersAtLeast: stats.distinctUsersOverflowed,\n setCount: stats.setCount,\n submissionCount: stats.submissionTotalKnown\n ? stats.submissionCount\n : undefined,\n threshold: config.minDistinctUsers,\n }),\n now,\n });\n if (created) {\n summary.created += 1;\n } else {\n summary.suppressed += 1;\n }\n }\n\n // -- default ---------------------------------------------------------\n // Dominance is measured against TOTAL submissions, never against\n // deviations alone: a value seen only in deviations would otherwise look\n // 100% dominant even when the default it would replace is what almost\n // everyone submits. Legacy buckets (no recorded total) make the\n // denominator unknown, so the group is skipped rather than guessed.\n if (\n isHistogramEligibleField(fieldDef) &&\n stats.submissionTotalKnown &&\n stats.submissionCount >= config.minSetCount\n ) {\n const top = topHistogramEntry(stats.histogram);\n if (top) {\n const share = top.count / stats.submissionCount;\n const proposed = decodeHistogramKey(fieldDef, top.key);\n const currentDefault = fieldPolicy.hasDefault\n ? fieldPolicy.defaultValue\n : undefined;\n if (\n share >= config.defaultDominanceRatio &&\n !sameProposedValue(proposed, currentDefault)\n ) {\n const created = await createUnlessSuppressed(suggestions, {\n tenantId: group.tenantId,\n objectRef: group.objectRef,\n fieldName: group.fieldName,\n kind: 'default',\n proposedValue: JSON.stringify(proposed),\n evidence: buildFieldUsageEvidence({\n kind: 'default',\n fieldName: group.fieldName,\n objectRef: group.objectRef,\n windowStart: fromPeriod,\n windowEnd: toPeriod,\n distinctUsers: stats.distinctUsers,\n distinctUsersAtLeast: stats.distinctUsersOverflowed,\n setCount: stats.setCount,\n submissionCount: stats.submissionCount,\n threshold: config.minSetCount,\n topValue: proposed,\n topValueShare: share,\n }),\n now,\n });\n if (created) {\n summary.created += 1;\n } else {\n summary.suppressed += 1;\n }\n }\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// The schedule target\n// ---------------------------------------------------------------------------\n\n/**\n * The registered schedule target for the #2051 learning loop (see the module\n * doc for why it is NOT an smrt-agents `Agent` subclass). Rows of its system\n * table are never written — the class exists so `AgentSchedule.agentType`\n * resolves through the `ObjectRegistry` and smrt-jobs can construct it and\n * invoke the two allowlisted methods.\n */\n@smrt({\n tableName: '_smrt_field_usage_learning_agents',\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldUsageLearningAgent extends SmrtObject {\n /**\n * The smrt-jobs opt-in background allowlist (S5 contract): ONLY these two\n * methods are reachable from a persisted job/schedule row.\n */\n static backgroundEligibleMethods: ReadonlyArray<string> = [\n 'runUsageMaintenance',\n 'runSuggestionGeneration',\n ];\n\n constructor(options: SmrtObjectOptions = {}) {\n super(options);\n }\n\n /** Schedule entry point for {@link runFieldUsageMaintenance}. */\n async runUsageMaintenance(\n args: Record<string, unknown> = {},\n ): Promise<FieldUsageMaintenanceSummary> {\n return runFieldUsageMaintenance({\n db: this.db,\n ...pickFiniteNumbers(args, [\n 'counterMaxAgeMs',\n 'counterMaxRows',\n 'suggestionAcceptedMaxAgeMs',\n ]),\n });\n }\n\n /** Schedule entry point for {@link runFieldPolicySuggestionGeneration}. */\n async runSuggestionGeneration(\n args: Record<string, unknown> = {},\n ): Promise<FieldUsageSuggestionRunSummary> {\n return runFieldPolicySuggestionGeneration({\n db: this.db,\n ...pickFiniteNumbers(args, [\n 'windowDays',\n 'minDistinctUsers',\n 'minSetCount',\n 'defaultDominanceRatio',\n ]),\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Evidence\n// ---------------------------------------------------------------------------\n\nexport interface FieldUsageEvidenceInput {\n kind: FieldPolicySuggestionKind;\n objectRef: string;\n fieldName: string;\n windowStart: string;\n windowEnd: string;\n distinctUsers: number;\n /** True when the distinct-user set overflowed (count is a lower bound). */\n distinctUsersAtLeast: boolean;\n /** Submissions that DIFFERED from the resolved default. */\n setCount: number;\n /**\n * TOTAL submissions observed in the window (the dominance denominator).\n * `undefined` only for legacy buckets that never recorded a total, in which\n * case the summary states the deviation count alone.\n */\n submissionCount?: number;\n /** The threshold the candidate cleared (documented in the evidence). */\n threshold: number;\n topValue?: unknown;\n topValueShare?: number;\n}\n\n/**\n * Human-readable evidence for a suggestion: a `summary` sentence a reviewer\n * can read as-is, plus the structured numbers behind it.\n *\n * The sentence always states BOTH numbers when known — deviations and total\n * submissions — so a reviewer can see the base rate a dominance percentage was\n * computed against instead of trusting a bare ratio.\n */\nexport function buildFieldUsageEvidence(\n input: FieldUsageEvidenceInput,\n): Record<string, unknown> {\n const users = `${input.distinctUsersAtLeast ? 'at least ' : ''}${\n input.distinctUsers\n } user${input.distinctUsers === 1 && !input.distinctUsersAtLeast ? '' : 's'}`;\n const deviations =\n `${input.setCount} of ` +\n (input.submissionCount === undefined\n ? 'an unrecorded number of submissions'\n : `${input.submissionCount} submission${\n input.submissionCount === 1 ? '' : 's'\n }`);\n const base =\n `${users} set \"${input.fieldName}\" to a non-default value in ` +\n `${deviations} between ${input.windowStart} and ${input.windowEnd}.`;\n const summary =\n input.kind === 'default'\n ? `${base} ${Math.round((input.topValueShare ?? 0) * 100)}% of all ` +\n `${input.submissionCount ?? 0} submissions used the value ` +\n `${JSON.stringify(input.topValue)}.`\n : base;\n\n return {\n summary,\n objectRef: input.objectRef,\n fieldName: input.fieldName,\n windowStart: input.windowStart,\n windowEnd: input.windowEnd,\n distinctUsers: input.distinctUsers,\n ...(input.distinctUsersAtLeast ? { distinctUsersAtLeast: true } : {}),\n setCount: input.setCount,\n ...(input.submissionCount !== undefined\n ? { submissionCount: input.submissionCount }\n : { submissionCountUnknown: true }),\n threshold: input.threshold,\n ...(input.topValue !== undefined ? { topValue: input.topValue } : {}),\n ...(input.topValueShare !== undefined\n ? { topValueShare: Number(input.topValueShare.toFixed(4)) }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface CounterGroup {\n tenantId: string;\n objectRef: string;\n fieldName: string;\n buckets: FieldUsageCounter[];\n}\n\nfunction groupCounters(rows: FieldUsageCounter[]): CounterGroup[] {\n const byKey = new Map<string, CounterGroup>();\n for (const row of rows) {\n if (!row.tenantId) {\n continue;\n }\n const key = `${row.tenantId}\\0${row.objectRef}\\0${row.fieldName}`;\n let group = byKey.get(key);\n if (!group) {\n group = {\n tenantId: row.tenantId,\n objectRef: row.objectRef,\n fieldName: row.fieldName,\n buckets: [],\n };\n byKey.set(key, group);\n }\n group.buckets.push(row);\n }\n return [...byKey.values()];\n}\n\ninterface GroupStats {\n /** Total submissions in the window (the dominance denominator). */\n submissionCount: number;\n /**\n * False when ANY bucket in the window predates the `submissionCount` column\n * (or is corrupt): the total is then unknown, so `default` suggestions are\n * skipped for the group rather than computed against a wrong denominator.\n */\n submissionTotalKnown: boolean;\n /** Submissions that differed from the resolved default. */\n setCount: number;\n distinctUsers: number;\n distinctUsersOverflowed: boolean;\n histogram: Record<string, number>;\n}\n\n/**\n * Merge a group's buckets: sum counts, UNION the capped distinct-user sets\n * (an overflowed bucket makes the union an honest lower bound), and sum\n * histogram buckets.\n */\nfunction mergeGroupStats(buckets: FieldUsageCounter[]): GroupStats {\n let submissionCount = 0;\n let submissionTotalKnown = true;\n let setCount = 0;\n let overflowed = false;\n const users = new Set<string>();\n // Null-prototype: histogram keys are user-supplied ids, so `constructor` /\n // `toString` / `__proto__` must accumulate as plain data (see\n // `emptyHistogram`). The `hasOwn` read below is the matching own-key test.\n const histogram = emptyHistogram();\n\n for (const bucket of buckets) {\n submissionCount += bucket.submissionCount;\n if (bucket.isLegacyBucket()) {\n submissionTotalKnown = false;\n }\n setCount += bucket.setCount;\n overflowed = overflowed || bucket.distinctUsersOverflowed;\n for (const id of bucket.getDistinctUserIds()) {\n users.add(id);\n }\n for (const [key, count] of Object.entries(bucket.getValueHistogram())) {\n histogram[key] =\n (Object.hasOwn(histogram, key) ? histogram[key] : 0) + count;\n }\n }\n\n return {\n submissionCount,\n submissionTotalKnown,\n setCount,\n distinctUsers: users.size,\n distinctUsersOverflowed: overflowed,\n histogram,\n };\n}\n\nfunction topHistogramEntry(\n histogram: Record<string, number>,\n): { key: string; count: number } | null {\n let top: { key: string; count: number } | null = null;\n for (const [key, count] of Object.entries(histogram)) {\n if (!top || count > top.count) {\n top = { key, count };\n }\n }\n return top;\n}\n\nfunction sameProposedValue(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true;\n }\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n}\n\nasync function createUnlessSuppressed(\n suggestions: FieldPolicySuggestionCollection,\n candidate: {\n tenantId: string;\n objectRef: string;\n fieldName: string;\n kind: FieldPolicySuggestionKind;\n proposedValue: string | null;\n evidence: Record<string, unknown>;\n now: Date;\n },\n): Promise<boolean> {\n const existing = await suggestions.list({\n where: {\n tenantId: candidate.tenantId,\n objectRef: candidate.objectRef,\n fieldName: candidate.fieldName,\n kind: candidate.kind,\n },\n });\n // Suppression keys off the ACTIVE SLOT, not `status`: a row holding\n // `activeKey === 'active'` is either pending or mid-decision (the\n // non-transactional accept holds the slot across its policy write). Keying\n // off `status` alone would let generation insert a competing pending row in\n // that window — which would then resolve against the pre-acceptance policy\n // and collide with the decision's compensation. Cooling-down dismissals\n // suppress too, even though they have released the slot.\n const suppressed = existing.some(\n (row) =>\n row.activeKey === ACTIVE_SUGGESTION_KEY ||\n (row.status === 'dismissed' && isWithinCoolDown(row, candidate.now)),\n );\n if (suppressed) {\n return false;\n }\n\n await suggestions.create({\n tenantId: candidate.tenantId,\n objectRef: candidate.objectRef,\n fieldName: candidate.fieldName,\n kind: candidate.kind,\n proposedValue: candidate.proposedValue,\n evidence: JSON.stringify(candidate.evidence),\n status: 'pending',\n });\n return true;\n}\n\nfunction isWithinCoolDown(row: FieldPolicySuggestion, now: Date): boolean {\n const raw = row.cooldownUntil as Date | string | null;\n if (raw === null || raw === undefined) {\n return false;\n }\n const until = raw instanceof Date ? raw.getTime() : Date.parse(String(raw));\n return Number.isFinite(until) && until > now.getTime();\n}\n\n/** Ambient-context restriction: non-bypass runs stay inside their tenant. */\nfunction restrictingTenantId(): string | null {\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return null;\n }\n return context.tenantId;\n}\n\n/**\n * Retention bounds are NON-NEGATIVE, not strictly positive: `0` is a meaningful\n * value the prune functions explicitly accept (`maxRows: 0` purges every row,\n * `maxAgeMs: 0` every bucket before today), so silently swapping it for the\n * 100k/90d default would ignore an operator's explicit \"purge everything\".\n * Only `undefined` (and non-finite junk) falls back to the default.\n */\nfunction normalizeMaintenanceConfig(\n options: Partial<FieldUsageMaintenanceConfig>,\n): FieldUsageMaintenanceConfig {\n return {\n counterMaxAgeMs: nonNegativeOrDefault(\n options.counterMaxAgeMs,\n FIELD_USAGE_MAINTENANCE_DEFAULTS.counterMaxAgeMs,\n ),\n counterMaxRows: nonNegativeOrDefault(\n options.counterMaxRows,\n FIELD_USAGE_MAINTENANCE_DEFAULTS.counterMaxRows,\n ),\n suggestionAcceptedMaxAgeMs: nonNegativeOrDefault(\n options.suggestionAcceptedMaxAgeMs,\n FIELD_USAGE_MAINTENANCE_DEFAULTS.suggestionAcceptedMaxAgeMs,\n ),\n };\n}\n\nfunction normalizeSuggestionConfig(\n options: Partial<FieldUsageSuggestionConfig>,\n): FieldUsageSuggestionConfig {\n const ratio = positiveOrDefault(\n options.defaultDominanceRatio,\n FIELD_USAGE_SUGGESTION_DEFAULTS.defaultDominanceRatio,\n );\n return {\n windowDays: Math.max(\n 1,\n Math.floor(\n positiveOrDefault(\n options.windowDays,\n FIELD_USAGE_SUGGESTION_DEFAULTS.windowDays,\n ),\n ),\n ),\n minDistinctUsers: Math.max(\n 1,\n Math.floor(\n positiveOrDefault(\n options.minDistinctUsers,\n FIELD_USAGE_SUGGESTION_DEFAULTS.minDistinctUsers,\n ),\n ),\n ),\n minSetCount: Math.max(\n 1,\n Math.floor(\n positiveOrDefault(\n options.minSetCount,\n FIELD_USAGE_SUGGESTION_DEFAULTS.minSetCount,\n ),\n ),\n ),\n defaultDominanceRatio: Math.min(Math.max(ratio, 0.01), 1),\n };\n}\n\n/** Strictly positive knobs (thresholds, windows): `0` is not meaningful. */\nfunction positiveOrDefault(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0\n ? value\n : fallback;\n}\n\n/** Retention bounds: `0` is meaningful (\"purge everything\"), so keep it. */\nfunction nonNegativeOrDefault(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? value\n : fallback;\n}\n\nfunction pickFiniteNumbers(\n args: Record<string, unknown>,\n keys: string[],\n): Record<string, number> {\n const picked: Record<string, number> = {};\n if (!args || typeof args !== 'object') {\n return picked;\n }\n for (const key of keys) {\n const value = args[key];\n if (typeof value === 'number' && Number.isFinite(value)) {\n picked[key] = value;\n }\n }\n return picked;\n}\n\nasync function deleteCounted(\n db: DatabaseInterface,\n condition: string,\n params: unknown[],\n table = '_smrt_field_usage_counters',\n): Promise<number> {\n const countRows = getQueryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM ${table} WHERE ${condition}`,\n ...params,\n ),\n );\n const total = numberFromRow(countRows[0] ?? {}, 'total');\n if (total > 0) {\n await db.query(`DELETE FROM ${table} WHERE ${condition}`, ...params);\n }\n return total;\n}\n\nfunction getQueryRows(result: unknown): Record<string, unknown>[] {\n return Array.isArray(result)\n ? (result as Record<string, unknown>[])\n : ((result as { rows?: Record<string, unknown>[] })?.rows ?? []);\n}\n\nfunction numberFromRow(row: Record<string, unknown>, key: string): number {\n const value = row[key];\n if (typeof value === 'number') {\n return value;\n }\n if (typeof value === 'bigint') {\n return Number(value);\n }\n if (typeof value === 'string') {\n return Number.parseFloat(value) || 0;\n }\n return 0;\n}\n","/**\n * Shared detection for OPTIONAL workspace dependencies\n * (`@happyvertical/smrt-users`, and since #2051 `@happyvertical/smrt-agents`\n * for the dormant learning schedules).\n *\n * A leaf module (no package-internal imports) so every dynamic-import seam —\n * the resolver's default tenant-hierarchy loader, the permission catalog\n * registration, and the schedule installer — shares one matcher without\n * creating an import cycle through the resolver/collection/model chain.\n */\n\n/** Node's missing-module message shapes, capturing the quoted specifier. */\nconst MISSING_MODULE_TARGET_PATTERN =\n /Cannot find (?:package|module) '([^']+)'/;\n\n/** Whether a missing-module TARGET specifier is `packageName` (or a subpath). */\nfunction isPackageSpecifier(target: string, packageName: string): boolean {\n return target === packageName || target.startsWith(`${packageName}/`);\n}\n\n/**\n * Whether an import failure means the named workspace package is simply not\n * installed (→ graceful degradation) rather than installed-but-broken\n * (→ rethrow, surfacing the problem instead of silently degrading).\n *\n * The decision is made on the missing-module TARGET parsed from Node's\n * `Cannot find package/module '<specifier>'` message (walking the full\n * `cause` chain): only a target that IS the package (or one of its subpaths)\n * counts. A transitive failure INSIDE an installed package names the other\n * module as the target — with the package path merely appearing as the\n * importer — and therefore rethrows. `importWorkspaceModule`'s own\n * source-fallback wrapper (`Failed to load <packageName> for ...`) is also\n * accepted: it is thrown only when the package itself cannot be located.\n *\n * Exported for direct testing; not re-exported from the package index.\n */\nexport function isMissingWorkspaceDependency(\n error: unknown,\n packageName: string,\n): boolean {\n let current: unknown = error;\n const seen = new Set<unknown>();\n\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current);\n\n const match = current.message.match(MISSING_MODULE_TARGET_PATTERN);\n if (match && isPackageSpecifier(match[1], packageName)) {\n return true;\n }\n\n if (current.message.includes(`Failed to load ${packageName} for`)) {\n return true;\n }\n\n current = current.cause;\n }\n\n return false;\n}\n\n/** {@link isMissingWorkspaceDependency} for `@happyvertical/smrt-users`. */\nexport function isMissingUsersDependency(error: unknown): boolean {\n return isMissingWorkspaceDependency(error, '@happyvertical/smrt-users');\n}\n","/**\n * Dormant `AgentSchedule` installation for the #2051 learning loop.\n *\n * `@happyvertical/smrt-agents` is an OPTIONAL dependency of this package\n * (the smrt-users seam): agents sits ABOVE fields in the dependency DAG (it\n * hard-depends on smrt-users, ai, and secrets — exactly the packages fields\n * keeps optional), so the installer dynamic-imports it and degrades\n * gracefully (`installed: false`) when it is not present. The schedule TARGET\n * (`FieldUsageLearningAgent`) needs no agents import at all — smrt-jobs\n * resolves `agentType` through the `ObjectRegistry`.\n *\n * DORMANT BY DEFAULT (the epic's suggestion-first posture): schedules are\n * created with `enabled: false` / `status: 'disabled'` unless the caller\n * explicitly opts in with `enabled: true` (or later runs the AgentSchedule\n * `enable()` operator command / flips the row). Activation is a deliberate\n * per-deployment decision documented in this package's AGENTS.md.\n */\n\nimport { importWorkspaceModule } from '@happyvertical/smrt-core/utils/import-workspace-module';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { deterministicFieldsUuid } from './deterministic-id.js';\nimport type {\n FieldUsageMaintenanceConfig,\n FieldUsageSuggestionConfig,\n} from './usage-learning.js';\nimport { isMissingWorkspaceDependency } from './users-module.js';\n\n/** Registry-qualified schedule target (`AgentSchedule.agentType`). */\nexport const FIELD_USAGE_LEARNING_AGENT_TYPE =\n '@happyvertical/smrt-fields:FieldUsageLearningAgent';\n\n/** Method the aggregation/retention schedule invokes. */\nexport const FIELD_USAGE_MAINTENANCE_METHOD = 'runUsageMaintenance';\n\n/** Method the suggestion-generation schedule invokes. */\nexport const FIELD_USAGE_SUGGESTION_METHOD = 'runSuggestionGeneration';\n\n/**\n * Default cadence for counter maintenance: daily at 02:30 — in the SCHEDULER\n * HOST'S LOCAL TIME. See {@link ensureFieldUsageLearningSchedules} for why no\n * timezone can be selected here.\n */\nexport const DEFAULT_FIELD_USAGE_MAINTENANCE_CRON = '30 2 * * *';\n\n/** Default cadence for suggestion generation: weekly, Monday 03:00 host-local. */\nexport const DEFAULT_FIELD_USAGE_SUGGESTION_CRON = '0 3 * * 1';\n\n/**\n * Stable id for a global learning schedule, derived from the agent type and\n * method (the `TenantUsageMetric.recordUsage` precedent).\n *\n * `_smrt_agent_schedules` has no natural-key uniqueness on\n * `(agent_type, method)`, so a check-then-create would let two replicas\n * starting against an empty database each insert their own random-id row —\n * and with `enabled: true` every job would then run twice. A deterministic id\n * makes the insert converge on ONE primary key instead.\n */\nexport function fieldUsageScheduleId(method: string): Promise<string> {\n return deterministicFieldsUuid([\n 'field-usage-learning-schedule',\n FIELD_USAGE_LEARNING_AGENT_TYPE,\n method,\n ]);\n}\n\n/**\n * Structural surface of the agents module the installer consumes (no static\n * import — mirrors `FieldPolicyUsersModule`). `create`/`list`/`save` are the\n * standard SmrtCollection/SmrtObject shapes.\n */\nexport interface FieldUsageAgentsScheduleRow {\n id?: string | null;\n enabled?: boolean;\n /**\n * Owning tenant; `null`/absent marks the GLOBAL schedules this installer\n * manages. Read so the existence check cannot mistake a tenant-specific\n * schedule for the global one.\n */\n tenantId?: string | null;\n save?: () => Promise<unknown>;\n}\n\nexport interface FieldUsageAgentsModule {\n AgentScheduleCollection: {\n create(options: { db: DatabaseInterface }): Promise<{\n list(options: {\n where: Record<string, unknown>;\n }): Promise<FieldUsageAgentsScheduleRow[]>;\n create(\n data: Record<string, unknown>,\n ): Promise<FieldUsageAgentsScheduleRow>;\n }>;\n };\n}\n\nexport interface EnsureFieldUsageLearningSchedulesOptions {\n db: DatabaseInterface;\n /**\n * Whether the schedules start enabled. DEFAULT FALSE — the learning loop\n * ships dormant; enabling it is an explicit deployment opt-in.\n */\n enabled?: boolean;\n maintenanceCron?: string;\n suggestionCron?: string;\n /** Threshold/retention overrides persisted into the schedules' methodArgs. */\n maintenanceArgs?: Partial<FieldUsageMaintenanceConfig>;\n suggestionArgs?: Partial<FieldUsageSuggestionConfig>;\n /** Injection seam for tests / hosts that already loaded the agents module. */\n agentsModule?: FieldUsageAgentsModule;\n}\n\nexport interface EnsureFieldUsageLearningSchedulesResult {\n /** False when `@happyvertical/smrt-agents` is not installed (no-op). */\n installed: boolean;\n /** Schedules created by THIS call (existing rows are left untouched). */\n created: number;\n}\n\n/**\n * Idempotently create the two GLOBAL (tenant-null) `AgentSchedule` rows for\n * the learning loop — the aggregation/retention roll-up and the\n * suggestion-generation job. An existing GLOBAL row for the agent type +\n * method is never modified (operator state like enable/disable is preserved).\n *\n * The existence check is scoped to the GLOBAL rows deliberately: a deployment\n * may also run tenant-specific schedules for the same agent type and method\n * (the ambient-context runs the jobs support), and matching one of those would\n * silently skip installing the global schedule this function promises. Tenant\n * rows are read but never touched.\n *\n * Concurrency: each schedule is written under a DETERMINISTIC id\n * ({@link fieldUsageScheduleId}) with insert-only semantics, so two replicas\n * installing at once converge on one row — the pre-check is only the cheap\n * path, and a primary-key collision is treated as \"already installed\" rather\n * than an error. Insert-only also means an existing row's operator state\n * (enabled/disabled, edited cron) is never overwritten by a later install.\n *\n * **No timezone option, deliberately.** `AgentSchedule` carries a `timezone`\n * column, but `getNextCronDate(cron, _timezone)` ignores the argument and\n * matches against host-local `getHours()`/`getDate()`, and smrt-jobs'\n * `ScheduleRunner` recalculates with the same host-local parser. Accepting a\n * timezone here would advertise control this stack does not have, so these\n * schedules fire in the SCHEDULER HOST'S LOCAL TIME — pick crons accordingly\n * (see `agents/usage-learning.md`). Fixing the agents-side parser is out of\n * scope for this package.\n *\n * System operation: global schedules are platform state, so a non-bypass\n * ambient tenant context is rejected (fail closed) — call this from trusted\n * startup/migration code.\n */\nexport async function ensureFieldUsageLearningSchedules(\n options: EnsureFieldUsageLearningSchedulesOptions,\n): Promise<EnsureFieldUsageLearningSchedulesResult> {\n const context = getCurrentTenant();\n if (context && !isSuperAdminBypass()) {\n throw new TenantIsolationError(\n 'ensureFieldUsageLearningSchedules installs GLOBAL schedules and must ' +\n 'run from trusted execution (no ambient tenant context, or ' +\n 'super-admin bypass)',\n { tenantId: context.tenantId },\n );\n }\n\n let agentsModule = options.agentsModule;\n if (!agentsModule) {\n try {\n agentsModule = await importWorkspaceModule<FieldUsageAgentsModule>({\n packageName: '@happyvertical/smrt-agents',\n sourceEntry: 'packages/agents/src/index.ts',\n purpose: 'field usage learning schedule installation',\n });\n } catch (error) {\n if (isMissingWorkspaceDependency(error, '@happyvertical/smrt-agents')) {\n return { installed: false, created: 0 };\n }\n throw error;\n }\n }\n\n const schedules = await agentsModule.AgentScheduleCollection.create({\n db: options.db,\n });\n const enabled = options.enabled ?? false;\n let created = 0;\n\n const definitions = [\n {\n method: FIELD_USAGE_MAINTENANCE_METHOD,\n cron: options.maintenanceCron ?? DEFAULT_FIELD_USAGE_MAINTENANCE_CRON,\n methodArgs: options.maintenanceArgs ?? {},\n },\n {\n method: FIELD_USAGE_SUGGESTION_METHOD,\n cron: options.suggestionCron ?? DEFAULT_FIELD_USAGE_SUGGESTION_CRON,\n methodArgs: options.suggestionArgs ?? {},\n },\n ];\n\n for (const definition of definitions) {\n const existing = await schedules.list({\n where: {\n agentType: FIELD_USAGE_LEARNING_AGENT_TYPE,\n method: definition.method,\n },\n });\n // Filter to the GLOBAL scope in memory rather than adding\n // `tenantId: null` to the where clause: an explicit `tenant_id IS NULL`\n // filter is what the tenancy interceptor flags as an isolation violation\n // (the reason `queryGlobal` exists), and this must also work under the\n // bypass context the guard above allows.\n if (existing.some(isGlobalScheduleRow)) {\n continue;\n }\n\n const id = await fieldUsageScheduleId(definition.method);\n let row: FieldUsageAgentsScheduleRow;\n try {\n row = await schedules.create({\n id,\n tenantId: null,\n agentType: FIELD_USAGE_LEARNING_AGENT_TYPE,\n agentId: null,\n cron: definition.cron,\n method: definition.method,\n agentConfig: {},\n methodArgs: definition.methodArgs,\n enabled,\n status: enabled ? 'active' : 'disabled',\n // Strict insert: a concurrent replica that already created this row\n // must NOT be adopted-and-overwritten (that would resurrect a\n // deliberately disabled schedule or clobber an edited cron).\n _insertOnly: true,\n });\n } catch (error) {\n // ONLY a confirmed collision may be swallowed. Constraint-error text is\n // not portable across sqlite/PostgreSQL/DuckDB (and core wraps driver\n // errors), so the evidence is the ROW ITSELF: re-read the deterministic\n // id and treat \"it exists now\" as proof another installer won the race.\n // Anything else — schema drift, validation, a dropped connection — must\n // propagate, or the installer would report success with the schedule\n // missing.\n const raced = await schedules.list({ where: { id } });\n if (raced.length === 0) {\n throw error;\n }\n continue;\n }\n // The schedulePersonaInstance precedent: an explicit save() runs the\n // model's beforeSave (next-run calculation) even if create() already\n // persisted the row.\n await row.save?.();\n created += 1;\n }\n\n return { installed: true, created };\n}\n\n/** Whether a schedule row is one of the GLOBAL (tenant-null) rows. */\nfunction isGlobalScheduleRow(row: FieldUsageAgentsScheduleRow): boolean {\n return row.tenantId === null || row.tenantId === undefined;\n}\n","/**\n * @happyvertical/smrt-fields\n *\n * Layered field policy store and resolver for SMRT objects (epic #2045):\n * per-field defaults, visibility tiers, help text, labels, ordering, and org\n * locks, personalized at app, tenant, and user scope over the code seed.\n *\n * @packageDocumentation\n */\n\n// Self-register this package's manifest before any @smrt() decorator fires\n// downstream. Must come first so the side effect runs ahead of the class\n// module loads below. See __smrt-register__.ts for issue #1132 context.\nimport './__smrt-register__.js';\n\nimport { ensureFieldPolicyPermissionsRegistered } from './permissions.js';\n\nexport {\n clearFieldPolicyCache,\n getFieldPolicyCacheTtlMs,\n invalidateFieldPolicyCache,\n} from './cache.js';\nexport { FieldPolicyCollection } from './collections/FieldPolicyCollection.js';\nexport {\n FieldPolicySuggestionCollection,\n FieldPolicySuggestionConflictError,\n} from './collections/FieldPolicySuggestionCollection.js';\nexport {\n decodeHistogramKey,\n FieldUsageCounterCollection,\n isHistogramEligibleField,\n MAX_USAGE_REPORT_ENTRIES,\n serializeHistogramSample,\n} from './collections/FieldUsageCounterCollection.js';\nexport {\n assertDefaultValueMatchesFieldType,\n buildCodeSeedDelta,\n buildCodeSeedVisibility,\n type FieldDefinitionMap,\n getCodeDefault,\n getCodeSeedGroup,\n getFieldReadPermission,\n getObjectFieldMap,\n isPolicyAddressableField,\n isRequiredField,\n isSensitiveField,\n isStorableReferenceId,\n isTransientField,\n isUsableRequiredDefault,\n type RegisteredFieldInfo,\n requireRegisteredObject,\n sanitizeFieldUIHints,\n} from './field-definitions.js';\nexport {\n resolveFieldPolicy,\n resolveFieldPolicyExplained,\n resolveSurvivingTenantChainIds,\n} from './field-policy-resolver.js';\nexport { FieldPolicy } from './models/FieldPolicy.js';\nexport {\n ACTIVE_SUGGESTION_KEY,\n FieldPolicySuggestion,\n} from './models/FieldPolicySuggestion.js';\nexport {\n FieldUsageCounter,\n fieldUsagePeriodForDate,\n MAX_DISTINCT_USERS_PER_BUCKET,\n MAX_VALUE_HISTOGRAM_BUCKETS,\n} from './models/FieldUsageCounter.js';\nexport {\n ensureFieldPolicyPermissionsRegistered,\n FIELD_POLICY_PERMISSION_DEFINITIONS,\n MANAGE_FIELD_POLICY_PERMISSION,\n PERSONALIZE_FIELD_POLICY_PERMISSION,\n} from './permissions.js';\nexport {\n type BuildFieldPolicySettingsCatalogOptions,\n buildFieldPolicySettingsCatalog,\n type FieldPolicyCatalogField,\n type FieldPolicyCatalogObjectSummary,\n type FieldPolicyDetailItem,\n type FieldPolicySettingsCatalogData,\n type FieldPolicySettingsCatalogPage,\n type FieldPolicySettingsCatalogQuery,\n type FieldPolicySummaryItem,\n fieldPolicyCatalogItemId,\n parseFieldPolicyCatalogQuery,\n} from './settings-catalog.js';\nexport {\n type AcceptFieldPolicySuggestionResult,\n APP_FIELD_POLICY_SCOPE_KEY,\n type DismissFieldPolicySuggestionResult,\n type ExplainedObjectFieldPolicy,\n FIELD_POLICY_SCOPE_TYPES,\n FIELD_POLICY_VISIBILITIES,\n type FieldPolicyAuditRow,\n type FieldPolicyAuditSnapshot,\n type FieldPolicyBatchResult,\n type FieldPolicyDelta,\n type FieldPolicyDriftReason,\n type FieldPolicyDriftRow,\n type FieldPolicyEditorCapabilities,\n type FieldPolicyEditorRow,\n type FieldPolicyEditorState,\n type FieldPolicyEditorStateDenied,\n type FieldPolicyEditorStateResult,\n type FieldPolicyLayerContribution,\n type FieldPolicyOptions,\n type FieldPolicyScopeType,\n type FieldPolicySuggestionData,\n type FieldPolicySuggestionKind,\n type FieldPolicySuggestionStatus,\n type FieldPolicyTenantHierarchyLoader,\n type FieldPolicyTenantHierarchyProvider,\n type FieldPolicyTenantNode,\n type FieldPolicyUsersModule,\n type FieldPolicyUsersTenantRecord,\n type FieldPolicyVisibility,\n type FieldUsageReportEntry,\n type FieldUsageReportResult,\n type PendingFieldPolicySuggestionsResult,\n type ResolvedFieldPolicy,\n type ResolvedObjectFieldPolicy,\n type ResolveFieldPolicyOptions,\n} from './types.js';\nexport {\n FieldUsageLearningAgent,\n pruneFieldPolicySuggestions,\n pruneFieldUsageCounters,\n runFieldPolicySuggestionGeneration,\n runFieldUsageMaintenance,\n} from './usage-learning.js';\nexport {\n ensureFieldUsageLearningSchedules,\n FIELD_USAGE_LEARNING_AGENT_TYPE,\n} from './usage-schedules.js';\n\n// Contribute the field-policy capabilities to the shared runtime catalog on\n// import, so normal role seeding and every server gate recognize the slugs.\nensureFieldPolicyPermissionsRegistered();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["policyRowId","claimed","unreadable"],"sources":["../src/__smrt-register__.ts","../src/deterministic-id.ts","../src/models/FieldPolicySuggestion.ts","../src/collections/FieldPolicySuggestionCollection.ts","../src/models/FieldUsageCounter.ts","../src/models/FieldUsageReportReceipt.ts","../src/collections/FieldUsageCounterCollection.ts","../src/data-surface.ts","../src/field-policy-resolver.ts","../src/settings-catalog.ts","../src/usage-learning.ts","../src/users-module.ts","../src/usage-schedules.ts","../src/index.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// During library builds, smrtPlugin replaces this entire module with generated\n// code that embeds the scanned manifest inline (#1506/#1507) — published dists\n// never resolve this URL, so downstream bundlers cannot break registration by\n// relocating the compiled module away from dist/manifest.json. The runtime\n// lookup below is the fallback for source-mode runs without that transform.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Deterministic row ids for this package's idempotent writes (the\n * `TenantUsageMetric.recordUsage` precedent).\n *\n * A leaf module (no package-internal imports) so both consumers — usage counter\n * buckets and the global learning schedules — share one implementation. The\n * output is formatted as a v5-shaped UUID so id columns stay native UUID on\n * PostgreSQL/DuckDB.\n */\n\n/**\n * SHA-256 over the namespaced parts, formatted as a v5-style UUID.\n *\n * The same parts always produce the same id, which is what turns\n * \"check then create\" into a race-free write: concurrent creators converge on\n * one primary key instead of inserting near-duplicate rows.\n */\nexport async function deterministicFieldsUuid(\n parts: readonly string[],\n): Promise<string> {\n const bytes = new TextEncoder().encode(JSON.stringify(parts));\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));\n const uuid = digest.slice(0, 16);\n uuid[6] = (uuid[6] & 0x0f) | 0x50;\n uuid[8] = (uuid[8] & 0x3f) | 0x80;\n const hex = Array.from(uuid, (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20),\n ].join('-');\n}\n","import {\n crossPackageRef,\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\nimport {\n assertDefaultValueMatchesFieldType,\n getFieldReadPermission,\n getObjectFieldMap,\n isSensitiveField,\n isTransientField,\n} from '../field-definitions.js';\nimport {\n FIELD_POLICY_SUGGESTION_KINDS,\n FIELD_POLICY_SUGGESTION_STATUSES,\n type FieldPolicySuggestionData,\n type FieldPolicySuggestionKind,\n type FieldPolicySuggestionStatus,\n} from '../types.js';\n\n/**\n * `activeKey` sentinel for the single ACTIVE (pending) suggestion per\n * `(objectRef, fieldName, tenantId, kind)`. Settled rows key themselves by id,\n * so history never competes for the slot.\n */\nexport const ACTIVE_SUGGESTION_KEY = 'active';\n\nexport interface FieldPolicySuggestionOptions extends SmrtObjectOptions {\n objectRef?: string;\n fieldName?: string;\n tenantId?: string;\n kind?: FieldPolicySuggestionKind;\n proposedValue?: string | null;\n evidence?: string;\n status?: FieldPolicySuggestionStatus;\n cooldownUntil?: Date | null;\n decidedBy?: string | null;\n decidedAt?: Date | null;\n}\n\n/**\n * A pending, human-reviewable field-policy improvement proposed from real\n * usage (epic #2045, issue #2051): promote a field to the `basic` tier, or\n * seed an org default with the dominant observed value.\n *\n * Suggestion-first by design — a row DOES NOTHING until a\n * `fields.policy.manage` holder accepts it, and acceptance writes the\n * org-scope {@link ../models/FieldPolicy.FieldPolicy} row through NORMAL\n * validation (registry check, type check, security rail, required-field\n * invariant, ownership + permission split). Dismissing sets a cool-down that\n * suppresses regeneration of the same suggestion.\n *\n * **One ACTIVE suggestion per identity, structurally** (not merely by a\n * check-then-insert): {@link activeKey} is a computed column holding the\n * sentinel {@link ACTIVE_SUGGESTION_KEY} while the row is `pending` and the\n * row's own id once it settles, and it participates in `conflictColumns`. The\n * unique index therefore admits at most ONE pending row per\n * `(objectRef, fieldName, tenantId, kind)` while every settled row keys\n * itself — so two overlapping generation runs (e.g. a global and a\n * tenant-specific schedule) UPSERT onto the same row instead of duplicating,\n * with no transaction spanning their reads. It is the `FieldPolicy.scopeKey`\n * trick applied to a lifecycle slot. On settle the column flips to the row's\n * id, which frees the slot for a post-cool-down regeneration while keeping the\n * dismissed/accepted history (and its id) intact — core conflicts a persisted\n * row on its primary key (#1472), so the flip is a plain UPDATE.\n *\n * #1885 seam: this substrate is fully independent of personas'\n * `DirectiveProposal` review queue (no shared producer discriminator —\n * deliberately out of scope). Tenant learning agents MAY create\n * FieldPolicySuggestion rows through this model's normal validation; the\n * reviewed `fields.policy.manage` acceptance gate is unchanged by who\n * proposed.\n */\n// Generated surfaces are CLOSED: reads would enumerate every tenant's\n// suggestion queue (the model is not class-level tenant-scoped, see below),\n// and generated writes would let callers forge evidence or flip status\n// without the gate. All access goes through the collection's scoped actions\n// (`pendingSuggestions`, `acceptSuggestion`, `dismissSuggestion`) or trusted\n// server-side code.\n@smrt({\n tableName: '_smrt_field_policy_suggestions',\n conflictColumns: [\n 'object_ref',\n 'field_name',\n 'tenant_id',\n 'kind',\n 'active_key',\n ],\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldPolicySuggestion extends SmrtObject {\n /** Qualified class name of the target object (`@package/name:ClassName`). */\n @field({ required: true })\n objectRef: string = '';\n\n /** Field name on the target object (validated against the registry). */\n @field({ required: true })\n fieldName: string = '';\n\n /** Owning tenant (required — suggestions always target one org). */\n @tenantId()\n tenantId?: string;\n\n /** What the suggestion proposes ('promote' | 'default'). */\n @field({ required: true })\n kind: FieldPolicySuggestionKind = 'promote';\n\n /**\n * JSON-encoded proposed default (`kind: 'default'` only) — the exact\n * encoding `FieldPolicy.defaultValue` stores, so acceptance passes it\n * through unchanged. NULL for `promote`.\n */\n @field({ type: 'text', nullable: true })\n proposedValue: string | null = null;\n\n /**\n * Human-readable evidence as a JSON string: a `summary` sentence plus the\n * structured window/threshold numbers behind it (see\n * `buildFieldUsageEvidence`).\n */\n @field({ type: 'text' })\n evidence: string = '{}';\n\n /** Lifecycle status ('pending' | 'accepted' | 'dismissed'). */\n @field({ required: true })\n status: FieldPolicySuggestionStatus = 'pending';\n\n /**\n * Computed lifecycle-slot key, set in `save()`: {@link ACTIVE_SUGGESTION_KEY}\n * while `pending`, else the row's own id. It exists ONLY to make the\n * `conflictColumns` unique index express \"at most one ACTIVE suggestion per\n * identity, unlimited settled history\" (the `FieldPolicy.scopeKey`\n * precedent) — never read it for logic; `status` owns that.\n */\n @field({ type: 'text', required: true })\n activeKey: string = ACTIVE_SUGGESTION_KEY;\n\n /**\n * Until this instant, a dismissed suggestion suppresses regeneration of the\n * same `(objectRef, fieldName, tenantId, kind)` suggestion. NULL until\n * dismissed.\n */\n @field({ type: 'datetime', nullable: true })\n cooldownUntil: Date | null = null;\n\n /** Who accepted/dismissed (audit attribution, #2050); not validated. */\n @crossPackageRef('@happyvertical/smrt-users:User', { nullable: true })\n decidedBy: string | null = null;\n\n /** When the suggestion was accepted/dismissed. */\n @field({ type: 'datetime', nullable: true })\n decidedAt: Date | null = null;\n\n constructor(options: FieldPolicySuggestionOptions = {}) {\n super(options);\n if (options.objectRef !== undefined) this.objectRef = options.objectRef;\n if (options.fieldName !== undefined) this.fieldName = options.fieldName;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.kind !== undefined) this.kind = options.kind;\n if (options.proposedValue !== undefined) {\n this.proposedValue = options.proposedValue;\n }\n if (options.evidence !== undefined) this.evidence = options.evidence;\n if (options.status !== undefined) this.status = options.status;\n if (options.cooldownUntil !== undefined) {\n this.cooldownUntil = options.cooldownUntil;\n }\n if (options.decidedBy !== undefined) this.decidedBy = options.decidedBy;\n if (options.decidedAt !== undefined) this.decidedAt = options.decidedAt;\n }\n\n /** Parse the stored evidence object (guarded; junk parses as empty). */\n getEvidence(): Record<string, unknown> {\n try {\n const parsed = JSON.parse(this.evidence);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n /** Serialize an evidence object into the stored JSON string. */\n setEvidence(evidence: Record<string, unknown>): void {\n this.evidence = JSON.stringify(evidence);\n }\n\n /** Parse the proposed value; `undefined` when none is stored. */\n getProposedValue(): unknown {\n if (this.proposedValue === null || this.proposedValue === undefined) {\n return undefined;\n }\n try {\n return JSON.parse(this.proposedValue);\n } catch {\n return undefined;\n }\n }\n\n /** Serialized row shape for the collection actions. */\n toSuggestionData(): FieldPolicySuggestionData {\n return {\n id: String(this.id),\n objectRef: this.objectRef,\n fieldName: this.fieldName,\n tenantId: String(this.tenantId ?? ''),\n kind: this.kind,\n proposedValue: this.proposedValue ?? null,\n evidence: this.getEvidence(),\n status: this.status,\n cooldownUntil: toIsoOrNull(this.cooldownUntil),\n decidedBy: this.decidedBy ?? null,\n decidedAt: toIsoOrNull(this.decidedAt),\n };\n }\n\n override async save(): Promise<this> {\n await this.assertRowOwnedByAmbientContext('save');\n await this.validateFieldPolicySuggestion();\n this.applyActiveKey();\n return super.save();\n }\n\n /**\n * Recompute the lifecycle-slot key: the shared sentinel while pending (so\n * the unique index admits exactly one), the row's own id once settled (so\n * history never competes for the slot and the freed slot allows a\n * post-cool-down regeneration). A settled row that has not been persisted\n * yet is assigned its id here — the key must be unique from the first write.\n */\n private applyActiveKey(): void {\n if (this.status === 'pending') {\n this.activeKey = ACTIVE_SUGGESTION_KEY;\n return;\n }\n if (!this.id) {\n this.id = crypto.randomUUID();\n }\n this.activeKey = String(this.id);\n }\n\n override async delete(): Promise<void> {\n await this.assertRowOwnedByAmbientContext('delete');\n await super.delete();\n }\n\n /**\n * The \"normal validation\" the #1885 seam promises producers: registry-known\n * field, policy-addressable, never sensitive/read-permission-gated/transient\n * (those fields are count-only in usage data and get no suggestions), valid\n * kind/status, and for `default` suggestions a JSON proposed value that\n * type-checks against the manifest field type.\n */\n private async validateFieldPolicySuggestion(): Promise<void> {\n if (!this.objectRef || this.objectRef.trim() === '') {\n throw new Error('FieldPolicySuggestion.objectRef is required');\n }\n if (!this.fieldName || this.fieldName.trim() === '') {\n throw new Error('FieldPolicySuggestion.fieldName is required');\n }\n if (!this.tenantId) {\n throw new Error('FieldPolicySuggestion.tenantId is required');\n }\n if (!FIELD_POLICY_SUGGESTION_KINDS.includes(this.kind)) {\n throw new Error(\n `FieldPolicySuggestion.kind must be one of ` +\n `${FIELD_POLICY_SUGGESTION_KINDS.join(', ')}; got \"${this.kind}\"`,\n );\n }\n if (!FIELD_POLICY_SUGGESTION_STATUSES.includes(this.status)) {\n throw new Error(\n `FieldPolicySuggestion.status must be one of ` +\n `${FIELD_POLICY_SUGGESTION_STATUSES.join(', ')}; got \"${this.status}\"`,\n );\n }\n\n const fields = await getObjectFieldMap(this.objectRef);\n const fieldDef = fields.get(this.fieldName);\n if (!fieldDef) {\n throw new Error(\n `Unknown field \"${this.fieldName}\" on \"${this.objectRef}\"`,\n );\n }\n if (\n fieldDef._meta?.__smrtSystemField === true ||\n fieldDef.type === 'oneToMany' ||\n fieldDef.type === 'manyToMany' ||\n fieldDef.type === 'meta'\n ) {\n throw new Error(\n `Field \"${this.fieldName}\" on \"${this.objectRef}\" is not ` +\n `policy-addressable, so it cannot carry a suggestion`,\n );\n }\n if (\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined ||\n isTransientField(fieldDef)\n ) {\n throw new Error(\n `Field \"${this.fieldName}\" on \"${this.objectRef}\" is sensitive, ` +\n `read-permission-gated, or transient; usage data for it is ` +\n `count-only and it cannot carry a suggestion`,\n );\n }\n\n if (this.kind === 'default') {\n if (this.proposedValue === null) {\n throw new Error(\n \"FieldPolicySuggestion of kind 'default' requires a proposedValue\",\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(this.proposedValue);\n } catch (error) {\n throw new Error(\n `FieldPolicySuggestion.proposedValue is not valid JSON: ` +\n `${error instanceof Error ? error.message : String(error)}`,\n );\n }\n assertDefaultValueMatchesFieldType(\n this.objectRef,\n this.fieldName,\n fieldDef,\n parsed,\n );\n } else if (this.proposedValue !== null) {\n throw new Error(\n \"FieldPolicySuggestion of kind 'promote' must not carry a proposedValue\",\n );\n }\n }\n\n /**\n * Tenant write boundary (the FieldPolicy posture): inside a non-bypass\n * tenant context a caller may only touch its own tenant's suggestions —\n * checked against BOTH the in-memory scope and, for persisted rows, the\n * PERSISTED tenant (a foreign row cannot be re-scoped into the caller's\n * tenant). Trusted execution (no context / bypass) is exempt — that is what\n * lets the scheduled generation job and platform flows operate.\n */\n private async assertRowOwnedByAmbientContext(\n operation: 'save' | 'delete',\n ): Promise<void> {\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return;\n }\n if (this.tenantId !== context.tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldPolicySuggestion.${operation}: ` +\n `context tenant is '${context.tenantId}' but the row belongs to ` +\n `'${this.tenantId}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: this.tenantId ?? undefined,\n },\n );\n }\n if (this.id) {\n const persisted = await this.db.get(this.tableName, { id: this.id });\n if (persisted) {\n const row = persisted as Record<string, unknown>;\n const persistedTenant =\n row.tenantId ?? row.tenant_id ?? this.tenantId ?? null;\n if (\n persistedTenant !== null &&\n String(persistedTenant) !== context.tenantId\n ) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldPolicySuggestion.` +\n `${operation}: the persisted row belongs to ` +\n `'${String(persistedTenant)}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: String(persistedTenant),\n },\n );\n }\n }\n }\n }\n}\n\nfunction toIsoOrNull(value: Date | string | null | undefined): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;\n}\n","import { SmrtCollection, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { assertOperationPermission } from '@happyvertical/smrt-users';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { invalidateFieldPolicyCache } from '../cache.js';\nimport { deterministicFieldsUuid } from '../deterministic-id.js';\nimport {\n assertDefaultValueMatchesFieldType,\n getFieldReadPermission,\n getObjectFieldMap,\n isPolicyAddressableField,\n isSensitiveField,\n isTransientField,\n} from '../field-definitions.js';\nimport {\n ACTIVE_SUGGESTION_KEY,\n FieldPolicySuggestion,\n} from '../models/FieldPolicySuggestion.js';\nimport { MANAGE_FIELD_POLICY_PERMISSION } from '../permissions.js';\nimport type {\n AcceptFieldPolicySuggestionResult,\n DismissFieldPolicySuggestionResult,\n PendingFieldPolicySuggestionsResult,\n} from '../types.js';\n\n/**\n * Raised when a pending→settled transition loses its race: another caller\n * (accept or dismiss) already settled the suggestion — whether the loss is\n * detected by the compare-and-set or by the load that preceded it, so\n * overlapping decisions get ONE error type regardless of timing.\n *\n * Carries BOTH `httpStatus` and `status` = 409. `httpStatus` is the property\n * core's generated REST error mapping honors (an integer OWN property, checked\n * with `Object.hasOwn`); `status` mirrors the users-style shape this package\n * already uses on `FieldPolicyPermissionError`. NOTE: that mapping arrived with\n * #2049 and is NOT in this branch's core yet, so until #2049 lands ahead of\n * this work the generated routes still surface these as 500s — the property is\n * set now so the 409 becomes real the moment it does.\n */\nexport class FieldPolicySuggestionConflictError extends Error {\n /** Core's generated-REST status contract (own integer property). */\n readonly httpStatus = 409;\n /** The users-style shape mirrored by this package's authorization errors. */\n readonly status = 409;\n\n constructor(operation: string, suggestionId: string) {\n super(\n `${operation}: suggestion \"${suggestionId}\" was already decided by ` +\n `another request (it is no longer pending)`,\n );\n this.name = 'FieldPolicySuggestionConflictError';\n }\n}\n\n/** Expected generated-route denial when the suggestion queue lacks a tenant. */\nclass FieldPolicySuggestionRequestContextError extends TenantIsolationError {\n readonly httpStatus = 403;\n readonly status = 403;\n\n constructor(message: string, details?: { tenantId?: string }) {\n super(message, details);\n this.name = 'FieldPolicySuggestionRequestContextError';\n }\n}\n\n/** Transaction handle shape (mirrors `FieldPolicy`'s identity-change path). */\ntype SuggestionTransactionHandle = DatabaseInterface & {\n commit: () => Promise<void>;\n rollback: () => Promise<void>;\n};\n\n/** Options binding a collection to an open transaction (the sales precedent). */\nfunction transactionBoundOptions(db: DatabaseInterface): {\n db: DatabaseInterface;\n _reuseInitializedDb: boolean;\n _deferRuntimeInitialization: boolean;\n} {\n return {\n db,\n // The transaction database is the SAME initialized database on a pinned\n // connection — skip system-table bootstrap and runtime service setup.\n _reuseInitializedDb: true,\n _deferRuntimeInitialization: true,\n };\n}\n\n/**\n * Compare-and-set the pending→settled transition: the UPDATE only applies\n * `WHERE status = 'pending'`, so exactly one of a racing accept/dismiss pair\n * can win. Returns whether this caller claimed it.\n *\n * `RETURNING id` (not a row count) is the reliable \"did it apply\" signal — the\n * DuckDB/JSON adapters report an UPDATE that matched nothing as `rowCount: 1`\n * (the jobs `writeOwnedJob` precedent).\n *\n * Deliberately raw SQL rather than a model save: a conditional transition is\n * not expressible through `save()`, and routing it through the model would\n * re-run PROPOSAL validation — which is exactly what wedges a stale suggestion\n * (its field may since have been removed or become gated). Ownership and the\n * manage permission are asserted by the caller before this runs.\n */\nasync function claimSuggestionTransition(\n db: DatabaseInterface,\n suggestionId: string,\n patch: {\n status: 'accepted' | 'dismissed';\n activeKey: string;\n decidedAt: Date;\n decidedBy: string | null;\n cooldownUntil: Date | null;\n },\n): Promise<boolean> {\n const result = await db.query(\n `UPDATE _smrt_field_policy_suggestions\n SET status = ?,\n active_key = ?,\n decided_at = ?,\n decided_by = ?,\n cooldown_until = ?\n WHERE id = ? AND status = 'pending'\n RETURNING id`,\n patch.status,\n patch.activeKey,\n patch.decidedAt.toISOString(),\n patch.decidedBy,\n patch.cooldownUntil ? patch.cooldownUntil.toISOString() : null,\n suggestionId,\n );\n return (result?.rows?.length ?? 0) > 0;\n}\n\n/**\n * Compensating revert for drivers without transactions: put a claimed row back\n * to `pending` after a refused policy write, so the suggestion is never left\n * settled without its policy. The active slot was held across the whole\n * decision, so this restores state the caller still owns.\n */\nasync function revertSuggestionToPending(\n db: DatabaseInterface,\n suggestionId: string,\n): Promise<void> {\n await db.query(\n `UPDATE _smrt_field_policy_suggestions\n SET status = 'pending',\n active_key = ?,\n decided_at = NULL,\n decided_by = NULL,\n cooldown_until = NULL\n WHERE id = ?`,\n ACTIVE_SUGGESTION_KEY,\n suggestionId,\n );\n}\n\n/**\n * Release the active slot after an accepted decision is fully durable: the row\n * keys itself by its own id, so a post-cool-down regeneration may take the\n * shared slot again while this settled row remains addressable.\n */\nasync function settleSuggestionActiveKey(\n db: DatabaseInterface,\n suggestionId: string,\n): Promise<void> {\n await db.query(\n `UPDATE _smrt_field_policy_suggestions\n SET active_key = ?\n WHERE id = ?`,\n suggestionId,\n suggestionId,\n );\n}\n\n/**\n * Raised when a non-transactional accept could neither complete its policy\n * write NOR undo its claim. Both causes are carried: the row may be sitting\n * `accepted` without a policy, which an operator must reconcile — silently\n * reporting only the policy error would imply the suggestion is still pending.\n */\nexport class FieldPolicySuggestionCompensationError extends Error {\n readonly httpStatus = 500;\n readonly status = 500;\n readonly revertCause: unknown;\n\n constructor(suggestionId: string, cause: unknown, revertCause: unknown) {\n super(\n `acceptSuggestion: the policy write for suggestion \"${suggestionId}\" ` +\n `failed AND reverting it to pending also failed; the suggestion may ` +\n `be left accepted without its policy row and needs reconciliation ` +\n `(policy error: ${cause instanceof Error ? cause.message : String(cause)}; ` +\n `revert error: ${\n revertCause instanceof Error\n ? revertCause.message\n : String(revertCause)\n })`,\n { cause },\n );\n this.name = 'FieldPolicySuggestionCompensationError';\n this.revertCause = revertCause;\n }\n}\n\n/**\n * Stable id for the org-scope policy row an acceptance creates, derived from\n * the policy natural key.\n *\n * With a random id, two concurrent acceptances for the same field would each\n * mint a row and the natural-key upsert would resolve them by REPLACING one\n * (dropping its column and dangling its id). A deterministic id turns that into\n * a primary-key collision the loser can detect and fall back from.\n */\nexport function fieldPolicyRowId(\n tenantId: string,\n objectRef: string,\n fieldName: string,\n): Promise<string> {\n return deterministicFieldsUuid([\n 'field-policy-row',\n 'tenant',\n tenantId,\n objectRef,\n fieldName,\n ]);\n}\n\n/**\n * Atomically set ONE column on the tenant-scope policy row for a field, keyed\n * by the policy natural key. Returns the surviving row id, or `null` when no\n * row exists yet.\n *\n * A single statement: concurrent acceptances of a `promote` and a `default`\n * suggestion for the same field touch disjoint columns and cannot drop each\n * other's write. `RETURNING id` (never a row count) is the reliable applied\n * signal — some adapters report a no-match UPDATE as `rowCount: 1`.\n */\nasync function updatePolicyColumn(\n db: DatabaseInterface,\n target: {\n objectRef: string;\n fieldName: string;\n tenantId: string;\n column: 'visibility' | 'default_value';\n value: string | null;\n decidedBy: string | null;\n },\n): Promise<string | null> {\n const result = await db.query(\n `UPDATE _smrt_field_policies\n SET ${target.column} = ?,\n updated_by = ?,\n updated_at = ?\n WHERE object_ref = ?\n AND field_name = ?\n AND scope_type = 'tenant'\n AND tenant_id = ?\n RETURNING id`,\n target.value,\n target.decidedBy,\n new Date().toISOString(),\n target.objectRef,\n target.fieldName,\n target.tenantId,\n );\n const rows = result?.rows ?? [];\n const id = rows[0]?.id;\n return id === undefined || id === null ? null : String(id);\n}\n\n/**\n * Re-apply the stored-default security rail at ACCEPTANCE time, through the\n * same shared helpers `FieldPolicy` and `FieldPolicySuggestion` validate with\n * (not a copy of the rules).\n *\n * A suggestion is validated when queued, but a field can turn sensitive,\n * `readPermission`-gated, transient, or change type before anyone accepts it —\n * and the atomic column update deliberately bypasses the model, so the rail is\n * asserted here instead of being silently skipped.\n */\nasync function assertAcceptedDefaultStillAllowed(\n suggestion: FieldPolicySuggestion,\n): Promise<void> {\n const fieldDef = await assertAcceptedPolicyTargetStillAllowed(suggestion);\n if (suggestion.proposedValue === null) {\n throw new Error(\n \"FieldPolicySuggestion of kind 'default' requires a proposedValue\",\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(suggestion.proposedValue);\n } catch (error) {\n throw new Error(\n `FieldPolicySuggestion.proposedValue is not valid JSON: ` +\n `${error instanceof Error ? error.message : String(error)}`,\n );\n }\n assertDefaultValueMatchesFieldType(\n suggestion.objectRef,\n suggestion.fieldName,\n fieldDef,\n parsed,\n );\n}\n\n/** Re-check every policy-addressability rail before an atomic acceptance write. */\nasync function assertAcceptedPolicyTargetStillAllowed(\n suggestion: FieldPolicySuggestion,\n) {\n const fields = await getObjectFieldMap(suggestion.objectRef);\n const fieldDef = fields.get(suggestion.fieldName);\n if (!fieldDef || !isPolicyAddressableField(fieldDef)) {\n throw new Error(\n `Field \"${suggestion.fieldName}\" on \"${suggestion.objectRef}\" ` +\n `is not policy-addressable`,\n );\n }\n if (\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined ||\n isTransientField(fieldDef)\n ) {\n throw new Error(\n `Cannot apply a policy for \"${suggestion.objectRef}.` +\n `${suggestion.fieldName}\": the field is sensitive, ` +\n `read-permission-gated, or transient`,\n );\n }\n return fieldDef;\n}\n\n/** Default cool-down a dismissal applies (30 days). */\nexport const DEFAULT_SUGGESTION_COOL_DOWN_MS = 30 * 24 * 60 * 60 * 1000;\n\n/** Bounds for a caller-supplied dismissal cool-down. */\nexport const MIN_SUGGESTION_COOL_DOWN_MS = 60 * 60 * 1000; // 1 hour\nexport const MAX_SUGGESTION_COOL_DOWN_MS = 365 * 24 * 60 * 60 * 1000; // 1 year\n\n/** Upper bound on objectRefs accepted by the pending filter. */\nconst MAX_PENDING_OBJECT_REFS = 100;\n\n/**\n * Collection surface for {@link FieldPolicySuggestion} plus the three\n * manage-gated actions the gear badge / control-panel queue consume (#2051;\n * UI integration is #2050-follow-up territory — this is the minimal seam).\n *\n * All three actions are custom collection-scoped routes (the resolveBatch\n * mechanism — single-segment paths, both transports dispatch). Identity is\n * ambient-context-only and every action requires `fields.policy.manage` (or\n * super-admin bypass) within an ambient tenant: pending suggestions describe\n * org-wide usage, and accept/dismiss are org policy decisions.\n */\n@smrt({\n // Mirror the item's active-slot natural key. The collection decorator emits\n // another schema for the same table; omitting this would reintroduce the\n // default `(slug, context)` unique index in generated migrations.\n conflictColumns: [\n 'object_ref',\n 'field_name',\n 'tenant_id',\n 'kind',\n 'active_key',\n ],\n api: {\n include: ['pendingSuggestions', 'acceptSuggestion', 'dismissSuggestion'],\n // Queue reads and decisions are scoped to the authenticated principal's\n // tenant and require fields.policy.manage; request payloads never select\n // that identity.\n principalContext: true,\n routes: {\n pendingSuggestions: {\n scope: 'collection',\n method: 'POST',\n path: 'pending',\n },\n acceptSuggestion: {\n scope: 'collection',\n method: 'POST',\n path: 'accept',\n },\n dismissSuggestion: {\n scope: 'collection',\n method: 'POST',\n path: 'dismiss',\n },\n },\n },\n cli: false,\n mcp: false,\n})\nexport class FieldPolicySuggestionCollection extends SmrtCollection<FieldPolicySuggestion> {\n static readonly _itemClass = FieldPolicySuggestion;\n\n /**\n * The caller's tenant's PENDING suggestions (optionally filtered to a set\n * of objectRefs), newest first, plus the total for the gear badge.\n */\n async pendingSuggestions(\n options: { objectRefs?: string[] } = {},\n ): Promise<PendingFieldPolicySuggestionsResult> {\n const tenantId = await this.requireManageContext('pendingSuggestions');\n const objectRefs = normalizeObjectRefsFilter(options.objectRefs);\n\n const where: Record<string, unknown> = { tenantId, status: 'pending' };\n if (objectRefs) {\n where['objectRef in'] = objectRefs;\n }\n const rows = await this.list({ where, orderBy: 'created_at DESC' });\n\n return {\n suggestions: rows.map((row) => row.toSuggestionData()),\n total: rows.length,\n };\n }\n\n /**\n * Accept a pending suggestion: CLAIM the pending→accepted transition, then\n * write the corresponding org-scope `FieldPolicy` row THROUGH NORMAL\n * VALIDATION (registry check, type check, security rail, required-field\n * invariant, ownership boundary, and the #2049 permission split — the\n * ambient caller must hold `fields.policy.manage`, which this action also\n * asserts up front).\n *\n * An existing tenant row for the same `(objectRef, fieldName)` is UPDATED\n * (read-modify-write), preserving its other sparse columns — never\n * duplicated through the natural-key upsert.\n *\n * Concurrency: the claim is a compare-and-set on `status` (the jobs\n * `writeOwnedJob` precedent — a guarded UPDATE with `RETURNING id`), so an\n * overlapping accept/dismiss pair cannot both win; the loser throws\n * {@link FieldPolicySuggestionConflictError} (409). Claim and policy write\n * run in ONE transaction when the driver supports it, so a rejected policy\n * write can never leave the suggestion settled without its policy (and vice\n * versa). Drivers without transactions get an explicit compensating revert.\n */\n async acceptSuggestion(\n options: { id?: string } = {},\n ): Promise<AcceptFieldPolicySuggestionResult> {\n const tenantId = await this.requireManageContext('acceptSuggestion');\n const suggestion = await this.loadOwnedPendingSuggestion(\n 'acceptSuggestion',\n options.id,\n tenantId,\n );\n const suggestionId = String(suggestion.id);\n const decidedBy = getCurrentTenant()?.userId ?? null;\n const decidedAt = new Date();\n\n const claim = {\n status: 'accepted' as const,\n activeKey: suggestionId,\n decidedAt,\n decidedBy,\n cooldownUntil: null,\n };\n\n const tx = await this.beginTransactionIfSupported();\n if (tx) {\n let policyRowId: string;\n try {\n const claimed = await claimSuggestionTransition(\n tx,\n suggestionId,\n claim,\n );\n if (!claimed) {\n throw new FieldPolicySuggestionConflictError(\n 'acceptSuggestion',\n suggestionId,\n );\n }\n policyRowId = await this.applyAcceptedPolicy(\n tx,\n suggestion,\n tenantId,\n decidedBy,\n );\n await tx.commit();\n } catch (error) {\n try {\n await tx.rollback();\n } catch {\n // Preserve the original failure; rollback errors are secondary.\n }\n throw error;\n }\n // The policy save inside the transaction invalidated the resolver cache\n // under the TRANSACTION handle's namespace, which is not the app db's —\n // so the committed change would otherwise stay invisible for the cache\n // TTL. Re-invalidate against this collection's db now that it is durable.\n invalidateFieldPolicyCache(suggestion.objectRef, this.db);\n suggestion.status = 'accepted';\n suggestion.decidedAt = decidedAt;\n suggestion.decidedBy = decidedBy;\n return { suggestion: suggestion.toSuggestionData(), policyRowId };\n }\n\n // No transaction support (e.g. a transaction VIEW, which exposes\n // `transaction` but not `beginTransaction`). Claim in TWO steps so the\n // identity's active slot is never free while the decision is in flight:\n //\n // 1. flip `status` to accepted but KEEP `activeKey = 'active'` — the\n // compare-and-set still makes exactly one decision win, while the\n // unique index keeps holding the slot, so generation running in this\n // window cannot insert a fresh pending suggestion that would then\n // collide with the compensation (and be resolved against the stale\n // pre-acceptance policy);\n // 2. only AFTER the policy write succeeds, settle the key to the row's\n // id, releasing the slot for a future post-cool-down regeneration.\n //\n // A refused policy write therefore reverts into a slot nothing else can\n // have taken. Generation's suppression keys off the slot (not `status`)\n // precisely so step 1 suppresses it.\n const claimed = await claimSuggestionTransition(this.db, suggestionId, {\n ...claim,\n activeKey: ACTIVE_SUGGESTION_KEY,\n });\n if (!claimed) {\n throw new FieldPolicySuggestionConflictError(\n 'acceptSuggestion',\n suggestionId,\n );\n }\n let policyRowId: string;\n try {\n policyRowId = await this.applyAcceptedPolicy(\n this.db,\n suggestion,\n tenantId,\n decidedBy,\n );\n } catch (error) {\n // Compensate back to pending. The slot was held throughout, so a\n // collision here is not an expected race — it means the row was mutated\n // underneath us and the compensation did NOT restore it. Surface that\n // instead of swallowing it: the caller must not be told the suggestion\n // is still pending when it may be stuck accepted-without-policy.\n try {\n await revertSuggestionToPending(this.db, suggestionId);\n } catch (revertError) {\n throw new FieldPolicySuggestionCompensationError(\n suggestionId,\n error,\n revertError,\n );\n }\n throw error;\n }\n\n // Release the slot now that the policy is durable.\n await settleSuggestionActiveKey(this.db, suggestionId);\n // The atomic column update bypasses the model, so it does not invalidate\n // the resolver cache the way `FieldPolicy.save()` does — do it here so org\n // forms shift immediately on this path too.\n invalidateFieldPolicyCache(suggestion.objectRef, this.db);\n\n suggestion.status = 'accepted';\n suggestion.decidedAt = decidedAt;\n suggestion.decidedBy = decidedBy;\n return { suggestion: suggestion.toSuggestionData(), policyRowId };\n }\n\n /**\n * Dismiss a pending suggestion: set status + a cool-down until which the\n * generation job will NOT regenerate the same\n * `(objectRef, fieldName, tenantId, kind)` suggestion.\n *\n * Validates OWNERSHIP and PENDING STATUS ONLY — deliberately NOT continued\n * proposal eligibility. A queued suggestion whose field has since been\n * removed, turned sensitive/`readPermission`-gated/transient, or retyped is\n * exactly the row an operator most needs to clear; re-running proposal\n * validation on the way out would reject the dismissal and, because pending\n * rows are never pruned, wedge it in the queue forever. The claim is the same\n * compare-and-set as accept, so it also bypasses the model's proposal\n * validation by construction.\n */\n async dismissSuggestion(\n options: { id?: string; coolDownMs?: number } = {},\n ): Promise<DismissFieldPolicySuggestionResult> {\n const tenantId = await this.requireManageContext('dismissSuggestion');\n const suggestion = await this.loadOwnedPendingSuggestion(\n 'dismissSuggestion',\n options.id,\n tenantId,\n );\n const suggestionId = String(suggestion.id);\n const coolDownMs = normalizeCoolDownMs(options.coolDownMs);\n const decidedAt = new Date();\n const decidedBy = getCurrentTenant()?.userId ?? null;\n const cooldownUntil = new Date(decidedAt.getTime() + coolDownMs);\n\n const claimed = await claimSuggestionTransition(this.db, suggestionId, {\n status: 'dismissed',\n activeKey: suggestionId,\n decidedAt,\n decidedBy,\n cooldownUntil,\n });\n if (!claimed) {\n throw new FieldPolicySuggestionConflictError(\n 'dismissSuggestion',\n suggestionId,\n );\n }\n\n suggestion.status = 'dismissed';\n suggestion.cooldownUntil = cooldownUntil;\n suggestion.decidedAt = decidedAt;\n suggestion.decidedBy = decidedBy;\n return { suggestion: suggestion.toSuggestionData() };\n }\n\n /**\n * Write the org-scope policy column an accepted suggestion implies, bound to\n * `db` so it participates in the caller's transaction.\n *\n * A `promote` and a `default` suggestion for the SAME field share one sparse\n * `FieldPolicy` row but own DIFFERENT columns, and both may be accepted\n * concurrently. A read-modify-save of the whole row therefore loses updates:\n * each acceptance reads the row (or its absence) before the other's save\n * lands, and the later full-row write erases the sibling's column — and, via\n * the natural-key upsert, can even replace the row id, dangling the earlier\n * caller's `policyRowId`. (Distinct from the suggestion-row race fixed\n * earlier: that one guards the pending→settled transition; this one is on the\n * shared policy row.) So this NEVER writes a whole row over an existing one:\n *\n * 1. An ATOMIC partial UPDATE sets only this acceptance's column, keyed by\n * the policy natural key and `RETURNING id` — one statement, so no\n * interleaving can drop the sibling's column, and the returned id is\n * always the surviving row.\n * 2. Only when no row exists yet does it CREATE through the model, so full\n * validation (registry, type check, security rail, required-field\n * invariant, ownership + permission split) runs on the row that is\n * actually inserted. The row carries a DETERMINISTIC id under strict\n * insert, so two concurrent creates cannot each mint a row: the loser\n * collides and falls back to step 1, applying only its own column.\n *\n * The update path skips the model, so BOTH kinds re-check live target\n * addressability through the shared helpers (a field can turn sensitive,\n * gated, transient, or disappear between queueing and acceptance). Defaults\n * additionally re-check their value/type rail; a promotion to `basic` has no\n * value payload, and the required-field invariant restricts only\n * advanced/hidden. Org locks constrain the user tier, not org rows.\n */\n private async applyAcceptedPolicy(\n db: DatabaseInterface,\n suggestion: FieldPolicySuggestion,\n tenantId: string,\n decidedBy: string | null,\n ): Promise<string> {\n if (suggestion.kind === 'default') {\n await assertAcceptedDefaultStillAllowed(suggestion);\n } else {\n await assertAcceptedPolicyTargetStillAllowed(suggestion);\n }\n\n const target = {\n objectRef: suggestion.objectRef,\n fieldName: suggestion.fieldName,\n tenantId,\n column:\n suggestion.kind === 'promote'\n ? ('visibility' as const)\n : ('default_value' as const),\n value: suggestion.kind === 'promote' ? 'basic' : suggestion.proposedValue,\n decidedBy,\n };\n\n const updatedId = await updatePolicyColumn(db, target);\n if (updatedId) {\n return updatedId;\n }\n\n // Dynamic import breaks the module cycle risk with the policy model\n // family (mirrors the resolver/collection seams in this package).\n const { FieldPolicyCollection } = await import(\n './FieldPolicyCollection.js'\n );\n const policies = await FieldPolicyCollection.create(\n transactionBoundOptions(db),\n );\n const deterministicId = await fieldPolicyRowId(\n tenantId,\n suggestion.objectRef,\n suggestion.fieldName,\n );\n\n try {\n const created = await policies.create({\n id: deterministicId,\n objectRef: suggestion.objectRef,\n fieldName: suggestion.fieldName,\n scopeType: 'tenant',\n tenantId,\n ...(suggestion.kind === 'promote'\n ? { visibility: 'basic' as const }\n : { defaultValue: suggestion.proposedValue }),\n updatedBy: decidedBy,\n // Strict insert: a concurrent acceptance that already created the row\n // must NOT be adopted-and-overwritten (that is the lost update).\n _insertOnly: true,\n });\n return String(created.id);\n } catch (error) {\n // Someone created the row between the update and the insert. Apply just\n // this acceptance's column to the surviving row.\n const racedId = await updatePolicyColumn(db, target);\n if (racedId) {\n return racedId;\n }\n throw error;\n }\n }\n\n /**\n * A driver transaction handle, or `null` when the driver has none (the\n * `FieldPolicy.saveAfterIdentityChange` probe, same shape).\n */\n private async beginTransactionIfSupported(): Promise<SuggestionTransactionHandle | null> {\n if (typeof this.db.beginTransaction !== 'function') {\n return null;\n }\n const tx = (await this.db.beginTransaction()) as\n | SuggestionTransactionHandle\n | undefined;\n return tx ?? null;\n }\n\n /**\n * Ambient manage gate shared by the three actions: an ambient tenant\n * context is required (no context ⇒ no identity ⇒ fail closed — the\n * suggestion queue is never an anonymous surface), and the caller must hold\n * `fields.policy.manage` unless running under super-admin bypass.\n */\n private async requireManageContext(action: string): Promise<string> {\n const context = getCurrentTenant();\n if (!context?.tenantId) {\n throw new FieldPolicySuggestionRequestContextError(\n `${action} requires an ambient tenant context (fail closed): the ` +\n 'suggestion queue is scoped to the caller tenant',\n );\n }\n if (!isSuperAdminBypass()) {\n await assertOperationPermission({\n collection: 'fields.policy',\n action: MANAGE_FIELD_POLICY_PERMISSION.split('.').at(-1) ?? 'manage',\n db: this.db,\n tenantId: context.tenantId,\n userId: context.userId ?? null,\n permissionSet: context.permissions,\n });\n }\n return context.tenantId;\n }\n\n private async loadOwnedPendingSuggestion(\n action: string,\n id: string | undefined,\n tenantId: string,\n ): Promise<FieldPolicySuggestion> {\n if (typeof id !== 'string' || id.trim() === '') {\n throw new Error(`${action} requires a suggestion \"id\" string`);\n }\n const suggestion = await this.get(id);\n if (!suggestion) {\n throw new Error(`${action}: no suggestion found for id \"${id}\"`);\n }\n if (suggestion.tenantId !== tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in ${action}: the suggestion belongs to ` +\n `another tenant`,\n {\n tenantId,\n attemptedTenantId: suggestion.tenantId ?? undefined,\n },\n );\n }\n if (suggestion.status !== 'pending') {\n // The SAME conflict the compare-and-set raises: whether a competing\n // decision landed before this request read the row or after, the caller\n // sees one error type (and one status) instead of a different failure\n // per interleaving.\n throw new FieldPolicySuggestionConflictError(action, id);\n }\n return suggestion;\n }\n}\n\nfunction normalizeObjectRefsFilter(\n rawRefs: string[] | undefined,\n): string[] | null {\n if (rawRefs === undefined) {\n return null;\n }\n if (!Array.isArray(rawRefs) || rawRefs.length === 0) {\n throw new Error(\n 'pendingSuggestions \"objectRefs\" must be a non-empty string array when provided',\n );\n }\n if (rawRefs.some((ref) => typeof ref !== 'string' || ref.trim() === '')) {\n throw new Error('pendingSuggestions objectRefs must be non-empty strings');\n }\n const objectRefs = [...new Set(rawRefs)];\n if (objectRefs.length > MAX_PENDING_OBJECT_REFS) {\n throw new Error(\n `pendingSuggestions accepts at most ${MAX_PENDING_OBJECT_REFS} ` +\n `objectRefs per call (got ${objectRefs.length})`,\n );\n }\n return objectRefs;\n}\n\nfunction normalizeCoolDownMs(raw: number | undefined): number {\n if (raw === undefined) {\n return DEFAULT_SUGGESTION_COOL_DOWN_MS;\n }\n if (typeof raw !== 'number' || !Number.isFinite(raw)) {\n throw new Error('dismissSuggestion coolDownMs must be a finite number');\n }\n return Math.min(\n Math.max(raw, MIN_SUGGESTION_COOL_DOWN_MS),\n MAX_SUGGESTION_COOL_DOWN_MS,\n );\n}\n","import {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n tenantId,\n} from '@happyvertical/smrt-tenancy';\n\n/**\n * Cap on distinct-user ids stored per bucket. For threshold questions\n * (\"did at least N distinct users set this field?\") the capped set is EXACT up\n * to the cap; once overflowed, {@link FieldUsageCounter.distinctUserCount} is\n * an honest LOWER BOUND that trivially satisfies any threshold ≤ the cap.\n */\nexport const MAX_DISTINCT_USERS_PER_BUCKET = 100;\n\n/** Cap on histogram buckets per counter row (bounded storage). */\nexport const MAX_VALUE_HISTOGRAM_BUCKETS = 25;\n\n/** Longest histogram key recorded; longer samples are skipped (count-only). */\nexport const MAX_VALUE_HISTOGRAM_KEY_LENGTH = 64;\n\n/** `period` bucket format: UTC calendar day. */\nexport const FIELD_USAGE_PERIOD_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** The UTC day bucket for a timestamp (`YYYY-MM-DD`). */\nexport function fieldUsagePeriodForDate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\n/**\n * A prototype-free histogram map.\n *\n * Histogram keys are user-supplied values (an `idType: 'text'` reference id may\n * legitimately be `constructor`, `toString`, or `__proto__`). On a plain object\n * those either resolve to inherited members — making an absent bucket look\n * present and corrupting its count — or, for `__proto__`, invoke the prototype\n * setter instead of creating an own key. A null-prototype object has no such\n * members, so every key behaves like data. Use this everywhere histogram counts\n * are accumulated (storage AND merge paths).\n */\nexport function emptyHistogram(): Record<string, number> {\n return Object.create(null) as Record<string, number>;\n}\n\nexport interface FieldUsageCounterOptions extends SmrtObjectOptions {\n objectRef?: string;\n fieldName?: string;\n tenantId?: string;\n period?: string;\n submissionCount?: number;\n setCount?: number;\n distinctUserCount?: number;\n distinctUserIds?: string;\n distinctUsersOverflowed?: boolean;\n valueHistogram?: string | null;\n valueHistogramOverflowed?: boolean;\n}\n\n/**\n * Period-bucketed field usage counter (epic #2045, issue #2051).\n *\n * One row aggregates field submissions for a single\n * `(objectRef, fieldName, tenantId, period)` — the substrate the\n * suggestion-generation job reads. Counters are deliberately APPROXIMATE:\n * ingestion is fire-and-forget and concurrent bucket merges may lose an\n * increment (read-modify-write), which is acceptable for usage statistics and\n * documented here rather than papered over.\n *\n * TWO counters, because they answer different questions:\n * - {@link submissionCount} — EVERY observed submission of the field\n * (default-matching or not). It is the denominator for value dominance:\n * \"N% of submissions used value V\". Without it, a value seen only in\n * deviations would look 100% dominant even against thousands of\n * default-valued submissions.\n * - {@link setCount} — submissions whose value DIFFERED from the resolved\n * default (server-derived). It plus {@link distinctUserIds} is the\n * promote signal (\"real users are actively filling this in\").\n *\n * Content rails (enforced by the ingestion action, which derives everything\n * from the live registry and never trusts the client):\n * - Sensitive and read-permission-gated fields are COUNT-ONLY: their raw\n * values are never recorded anywhere in usage data — not even for\n * default-matching submissions.\n * - Value histograms exist only for low-cardinality field types (`boolean`,\n * `foreignKey`, `crossPackageRef`) — never free text, even non-sensitive\n * text (PII risk) — with a bounded bucket count and key length. They cover\n * ALL submissions (not just deviations) so the dominance ratio is a true\n * fraction of {@link submissionCount}.\n * - `distinctUserIds` is a capped set with an overflow marker (see\n * {@link MAX_DISTINCT_USERS_PER_BUCKET} for the honesty contract).\n */\n// All generated surfaces are CLOSED: rows aggregate cross-tenant usage, so\n// list/get would leak other tenants' activity and create/update/delete would\n// let clients forge counters. The ONLY write path is the collection's\n// `reportUsage` action (ambient-identity, fail closed); reads happen\n// server-side in the learning jobs.\n@smrt({\n tableName: '_smrt_field_usage_counters',\n conflictColumns: ['object_ref', 'field_name', 'tenant_id', 'period'],\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldUsageCounter extends SmrtObject {\n /** Qualified class name of the target object (`@package/name:ClassName`). */\n @field({ required: true })\n objectRef: string = '';\n\n /** Field name on the target object. */\n @field({ required: true })\n fieldName: string = '';\n\n /**\n * Owning tenant. REQUIRED: ingestion fails closed without an ambient tenant\n * context, so every row is attributable (and the conflict-column tuple\n * stays total). Native UUID on PostgreSQL/DuckDB.\n */\n @tenantId()\n tenantId?: string;\n\n /** UTC day bucket (`YYYY-MM-DD`); lexicographic order is time order. */\n @field({ required: true })\n period: string = '';\n\n /**\n * EVERY observed submission of this field in the bucket, whether or not the\n * value matched the resolved default — the dominance denominator.\n *\n * Rows written before this column existed carry `0` while `setCount > 0`;\n * {@link isLegacyBucket} detects that shape and the generation job then\n * treats the total as UNKNOWN and skips `default` suggestions for the group\n * (promote, which needs no denominator, still works).\n */\n @field({ type: 'integer' })\n submissionCount: number = 0;\n\n /**\n * Submissions whose value DIFFERED from the server-resolved default (the\n * promote signal). Always `<= submissionCount` on rows written by the\n * current ingestion path.\n */\n @field({ type: 'integer' })\n setCount: number = 0;\n\n /**\n * Size of the stored distinct-user set. When\n * {@link distinctUsersOverflowed} is true this is a LOWER BOUND (the set is\n * capped), never an estimate.\n */\n @field({ type: 'integer' })\n distinctUserCount: number = 0;\n\n /** JSON array of distinct user ids, capped (see the class doc). */\n @field({ type: 'text' })\n distinctUserIds: string = '[]';\n\n /** True once a distinct user was NOT added because the set is at its cap. */\n @field({ type: 'boolean' })\n distinctUsersOverflowed: boolean = false;\n\n /**\n * JSON object `serializedValue -> count` for histogram-eligible fields;\n * NULL when the field is count-only. Keys are bounded in number and length.\n */\n @field({ type: 'text', nullable: true })\n valueHistogram: string | null = null;\n\n /** True once a sample was dropped because the bucket cap was reached. */\n @field({ type: 'boolean' })\n valueHistogramOverflowed: boolean = false;\n\n constructor(options: FieldUsageCounterOptions = {}) {\n super(options);\n if (options.objectRef !== undefined) this.objectRef = options.objectRef;\n if (options.fieldName !== undefined) this.fieldName = options.fieldName;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.period !== undefined) this.period = options.period;\n if (options.submissionCount !== undefined) {\n this.submissionCount = options.submissionCount;\n }\n if (options.setCount !== undefined) this.setCount = options.setCount;\n if (options.distinctUserCount !== undefined) {\n this.distinctUserCount = options.distinctUserCount;\n }\n if (options.distinctUserIds !== undefined) {\n this.distinctUserIds = options.distinctUserIds;\n }\n if (options.distinctUsersOverflowed !== undefined) {\n this.distinctUsersOverflowed = options.distinctUsersOverflowed;\n }\n if (options.valueHistogram !== undefined) {\n this.valueHistogram = options.valueHistogram;\n }\n if (options.valueHistogramOverflowed !== undefined) {\n this.valueHistogramOverflowed = options.valueHistogramOverflowed;\n }\n }\n\n /** Parse the stored distinct-user set (guarded; junk parses as empty). */\n getDistinctUserIds(): string[] {\n try {\n const parsed = JSON.parse(this.distinctUserIds);\n return Array.isArray(parsed)\n ? parsed.filter((id): id is string => typeof id === 'string')\n : [];\n } catch {\n return [];\n }\n }\n\n /**\n * Add a user to the distinct set, honoring the cap. At the cap the id is\n * NOT added and the overflow marker is set instead, keeping\n * {@link distinctUserCount} an honest lower bound.\n */\n addDistinctUser(userId: string): void {\n const ids = this.getDistinctUserIds();\n if (ids.includes(userId)) {\n return;\n }\n if (ids.length >= MAX_DISTINCT_USERS_PER_BUCKET) {\n this.distinctUsersOverflowed = true;\n return;\n }\n ids.push(userId);\n this.distinctUserIds = JSON.stringify(ids);\n this.distinctUserCount = ids.length;\n }\n\n /**\n * Parse the stored histogram (guarded; junk parses as empty).\n *\n * Returns a NULL-PROTOTYPE object. Histogram keys are user-supplied ids —\n * an `idType: 'text'` reference may legitimately be `constructor`,\n * `toString`, or `__proto__` — and on a plain object those inherit truthy\n * prototype values (so a missing bucket reads as present) or, for\n * `__proto__`, hit the prototype setter instead of creating an own key.\n * Both would silently corrupt counts. See {@link emptyHistogram}.\n */\n getValueHistogram(): Record<string, number> {\n const histogram = emptyHistogram();\n if (!this.valueHistogram) {\n return histogram;\n }\n try {\n const parsed = JSON.parse(this.valueHistogram);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return histogram;\n }\n // `Object.entries` yields OWN enumerable keys only, and `JSON.parse`\n // materializes `__proto__` as an ordinary own data property, so the\n // round trip preserves every legitimate id.\n for (const [key, count] of Object.entries(parsed)) {\n if (typeof count === 'number' && Number.isFinite(count) && count > 0) {\n histogram[key] = count;\n }\n }\n return histogram;\n } catch {\n return emptyHistogram();\n }\n }\n\n /**\n * Record one histogram sample under an already-serialized key, honoring the\n * bucket cap (a NEW key past the cap is dropped and the overflow marker\n * set; existing keys keep counting).\n *\n * Bucket presence is an OWN-key test, never a truthiness/`undefined` read,\n * so prototype-shaped ids behave like any other key.\n */\n recordHistogramSample(key: string): void {\n if (key.length === 0 || key.length > MAX_VALUE_HISTOGRAM_KEY_LENGTH) {\n return;\n }\n const histogram = this.getValueHistogram();\n if (!Object.hasOwn(histogram, key)) {\n if (Object.keys(histogram).length >= MAX_VALUE_HISTOGRAM_BUCKETS) {\n this.valueHistogramOverflowed = true;\n return;\n }\n histogram[key] = 1;\n } else {\n histogram[key] += 1;\n }\n this.valueHistogram = JSON.stringify(histogram);\n }\n\n /**\n * Whether this bucket predates the {@link submissionCount} column (or was\n * corrupted): it records deviations without a total, so no honest dominance\n * ratio can be computed from it. The generation job skips `default`\n * suggestions for any group containing such a bucket.\n */\n isLegacyBucket(): boolean {\n return this.submissionCount < this.setCount;\n }\n\n override async save(): Promise<this> {\n await this.assertRowOwnedByAmbientContext('save');\n this.validateFieldUsageCounter();\n return super.save();\n }\n\n override async delete(): Promise<void> {\n await this.assertRowOwnedByAmbientContext('delete');\n await super.delete();\n }\n\n private validateFieldUsageCounter(): void {\n if (!this.objectRef || this.objectRef.trim() === '') {\n throw new Error('FieldUsageCounter.objectRef is required');\n }\n if (!this.fieldName || this.fieldName.trim() === '') {\n throw new Error('FieldUsageCounter.fieldName is required');\n }\n if (!this.tenantId) {\n throw new Error('FieldUsageCounter.tenantId is required');\n }\n if (!FIELD_USAGE_PERIOD_PATTERN.test(this.period)) {\n throw new Error(\n `FieldUsageCounter.period must be a UTC day bucket (YYYY-MM-DD); ` +\n `got \"${this.period}\"`,\n );\n }\n if (!Number.isInteger(this.submissionCount) || this.submissionCount < 0) {\n throw new Error(\n 'FieldUsageCounter.submissionCount must be a non-negative integer',\n );\n }\n if (!Number.isInteger(this.setCount) || this.setCount < 0) {\n throw new Error(\n 'FieldUsageCounter.setCount must be a non-negative integer',\n );\n }\n if (\n !Number.isInteger(this.distinctUserCount) ||\n this.distinctUserCount < 0\n ) {\n throw new Error(\n 'FieldUsageCounter.distinctUserCount must be a non-negative integer',\n );\n }\n }\n\n /**\n * Tenant write boundary (the FieldPolicy posture — no class-level\n * `@TenantScoped`, because the learning jobs legitimately operate\n * cross-tenant in trusted execution): inside a non-bypass tenant context a\n * caller may only touch rows of its own tenant; without a context (system/\n * job execution) writes are trusted.\n *\n * Checked against BOTH the in-memory tenant and — for a row that already\n * exists — the PERSISTED one. The persisted check is what makes the boundary\n * real: bucket ids are deterministic and the deriving helper is exported, so\n * a foreign row is trivially addressable, and an in-memory-only check would\n * let a caller load it, re-stamp `tenantId` with its own, and adopt or delete\n * another tenant's counters (the #2047 FieldPolicy pattern).\n */\n private async assertRowOwnedByAmbientContext(\n operation: 'save' | 'delete',\n ): Promise<void> {\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return;\n }\n if (this.tenantId !== context.tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldUsageCounter.${operation}: ` +\n `context tenant is '${context.tenantId}' but the row belongs to ` +\n `'${this.tenantId}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: this.tenantId ?? undefined,\n },\n );\n }\n\n const persistedTenantId = await this.getPersistedTenantId();\n if (persistedTenantId !== null && persistedTenantId !== context.tenantId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in FieldUsageCounter.${operation}: the ` +\n `persisted row belongs to '${persistedTenantId}'`,\n {\n tenantId: context.tenantId,\n attemptedTenantId: persistedTenantId,\n },\n );\n }\n }\n\n /** The stored tenant for this row's id; `null` when it is not persisted. */\n private async getPersistedTenantId(): Promise<string | null> {\n if (!this.id) {\n return null;\n }\n const existing = await this.db.get(this.tableName, { id: this.id });\n if (!existing) {\n return null;\n }\n const row = existing as Record<string, unknown>;\n const value = row.tenantId ?? row.tenant_id;\n return value === undefined || value === null ? null : String(value);\n }\n}\n","import {\n crossPackageRef,\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { tenantId } from '@happyvertical/smrt-tenancy';\n\n/**\n * Durable daily receipt for one member's contribution to one field.\n *\n * The counter action creates this before incrementing its aggregate. Its\n * natural key makes the anti-inflation rule durable across requests and\n * replicas: one `(tenant, user, object, field, UTC day)` sample may affect\n * usage evidence. Receipts intentionally retain no submitted value.\n */\n@smrt({\n tableName: '_smrt_field_usage_report_receipts',\n conflictColumns: [\n 'tenant_id',\n 'user_id',\n 'object_ref',\n 'field_name',\n 'period',\n ],\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldUsageReportReceipt extends SmrtObject {\n @tenantId()\n tenantId?: string;\n\n @crossPackageRef('@happyvertical/smrt-users:User')\n userId: string = '';\n\n @field({ required: true })\n objectRef: string = '';\n\n @field({ required: true })\n fieldName: string = '';\n\n @field({ required: true })\n period: string = '';\n\n constructor(options: FieldUsageReportReceiptOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.userId !== undefined) this.userId = options.userId;\n if (options.objectRef !== undefined) this.objectRef = options.objectRef;\n if (options.fieldName !== undefined) this.fieldName = options.fieldName;\n if (options.period !== undefined) this.period = options.period;\n }\n}\n\nexport interface FieldUsageReportReceiptOptions extends SmrtObjectOptions {\n tenantId?: string;\n userId?: string;\n objectRef?: string;\n fieldName?: string;\n period?: string;\n}\n","import { SmrtCollection, smrt } from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { deterministicFieldsUuid } from '../deterministic-id.js';\nimport {\n type FieldDefinitionMap,\n getFieldReadPermission,\n getObjectFieldMap,\n isSensitiveField,\n isStorableReferenceId,\n isTransientField,\n type RegisteredFieldInfo,\n} from '../field-definitions.js';\nimport {\n FieldUsageCounter,\n fieldUsagePeriodForDate,\n MAX_VALUE_HISTOGRAM_KEY_LENGTH,\n} from '../models/FieldUsageCounter.js';\nimport { FieldUsageReportReceipt } from '../models/FieldUsageReportReceipt.js';\nimport type {\n FieldUsageReportEntry,\n FieldUsageReportResult,\n ResolvedFieldPolicy,\n} from '../types.js';\n\n/**\n * Deviation comparison: strict equality, then a JSON round-trip so structured\n * and Date-shaped values compare by serialization. Mirrors the browser-side\n * `fieldUsageValuesEqual` (the packaging boundary bars value imports across the\n * subpath split — the permission-slug precedent); a node test pins the two\n * behaviours equal.\n */\nexport function usageValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true;\n }\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n}\n\n/**\n * Per-call bound on reported entries — the rate rail for the fire-and-forget\n * ingestion action (the resolveBatch precedent). The browser deliberately\n * does not impose a duplicate limit: every report is server-bounded here.\n */\nexport const MAX_USAGE_REPORT_ENTRIES = 100;\n\n/** Expected generated-route denial for a missing trusted report principal. */\nclass FieldUsageRequestContextError extends TenantIsolationError {\n readonly httpStatus = 403;\n readonly status = 403;\n\n constructor(message: string, details?: { tenantId?: string }) {\n super(message, details);\n this.name = 'FieldUsageRequestContextError';\n }\n}\n\n/**\n * Collection surface for {@link FieldUsageCounter} plus the batched usage\n * ingestion action (#2051).\n *\n * `reportUsage` is a custom collection-scoped action (the resolveBatch\n * mechanism — single-segment path, so the generated SvelteKit transport AND\n * core's runtime `APIGenerator` both dispatch it). Everything else is CLOSED:\n * no generated CRUD, no CLI, no MCP — counters are written only through this\n * action and read only by trusted server-side code (the learning jobs).\n */\n@smrt({\n // A decorated collection emits its own schema for the item's table. Mirror\n // the model natural key so manifest-driven migrations cannot add the\n // fallback `(slug, context)` unique index to this shared system table.\n conflictColumns: ['object_ref', 'field_name', 'tenant_id', 'period'],\n api: {\n include: ['reportUsage'],\n // The action derives tenant/user exclusively from the authenticated\n // request context; generated routes must establish that context before\n // dispatching it (the FieldPolicyCollection precedent).\n principalContext: true,\n routes: {\n reportUsage: {\n scope: 'collection',\n method: 'POST',\n path: 'report',\n },\n },\n },\n cli: false,\n mcp: false,\n})\nexport class FieldUsageCounterCollection extends SmrtCollection<FieldUsageCounter> {\n static readonly _itemClass = FieldUsageCounter;\n\n /**\n * Record a batch of field submissions into the current UTC-day counters.\n *\n * Identity is AMBIENT-ONLY (the resolveBatch posture): the tenant AND the\n * user come exclusively from the tenant context established by the app's\n * auth hook — the request body cannot attribute usage to another tenant or\n * user. Both are REQUIRED and the call fails closed without them (#2047\n * round-3 posture):\n *\n * - No ambient tenant ⇒ the report is unattributable.\n * - No ambient USER ⇒ the caller is an unauthenticated principal (a\n * deployment that resolves the tenant from host/header alone). Such\n * callers could otherwise inflate counts and histograms without bound\n * (the batch cap is per call), and `distinctUsers` — the promote signal —\n * is meaningless without a principal. Consequence, accepted deliberately:\n * ANONYMOUS/PUBLIC FORMS DO NOT CONTRIBUTE USAGE. The learning loop is\n * for authenticated org users.\n *\n * Any authenticated in-tenant principal may report (no manage slug needed).\n * A durable receipt admits at most one `(tenant, user, object, field, UTC\n * day)` contribution, and the batch stays bounded per call\n * ({@link MAX_USAGE_REPORT_ENTRIES}).\n *\n * Server-side derivation (the client is never the authority):\n * - Fields are validated against the live `ObjectRegistry`; unknown or\n * non-usage-addressable entries (system, relationship pseudo-fields, STI\n * meta storage, transient) are DROPPED and counted, never failing the\n * batch (stale clients after a redeploy are expected).\n * - Sensitivity comes from BOTH `field.sensitive` and `field._meta.sensitive`\n * (and `readPermission` in both places): such fields are recorded\n * COUNT-ONLY — their values are never persisted anywhere in usage data,\n * default-matching or not.\n * - DEVIATION (`setCount`, distinct users) is decided by comparing the\n * submitted value against the default RESOLVED HERE for the calling\n * identity — never against a client claim. Every accepted entry also\n * increments `submissionCount`, the dominance denominator.\n * - Values are histogrammed only for low-cardinality field types\n * (`boolean`, `foreignKey`, `crossPackageRef`) with type-checked samples;\n * free text is never histogrammed.\n *\n * Bounded trust in `matchedDefault`: an entry may omit `value` entirely\n * (`collectFieldUsageEntries({ includeValues: false })` — apps that prefer\n * no value transit). Only then is the entry's `matchedDefault` flag read, to\n * decide the deviation bit alone. It can never create a histogram entry,\n * never bypass the count-only rail, and never manufacture distinct users\n * beyond the caller's own id. The once-per-field/day receipt means a lying\n * client gets only one such contribution, so repeated POSTs cannot inflate\n * thresholds or steer a `default` suggestion.\n */\n async reportUsage(\n options: { entries?: FieldUsageReportEntry[] } = {},\n ): Promise<FieldUsageReportResult> {\n const entries = validateEntriesInput(options.entries);\n\n const context = getCurrentTenant();\n const tenantId = context?.tenantId;\n if (!tenantId) {\n throw new FieldUsageRequestContextError(\n 'reportUsage requires an ambient tenant context: usage is ' +\n 'unattributable without one, so the call fails closed',\n );\n }\n const userId = context.userId;\n if (!userId) {\n throw new FieldUsageRequestContextError(\n 'reportUsage requires an ambient AUTHENTICATED user: an ' +\n 'unauthenticated caller could inflate counters without bound and ' +\n 'distinct-user thresholds are meaningless without a principal, so ' +\n 'the call fails closed (anonymous forms do not contribute usage)',\n { tenantId },\n );\n }\n const period = fieldUsagePeriodForDate(new Date());\n const receipts = await FieldUsageReportReceiptCollection.create({\n db: this.db,\n });\n\n // Group by (objectRef, fieldName) so one bucket row is touched once per\n // batch (bounds registry and db work per call).\n const groups = new Map<string, Map<string, FieldUsageReportEntry[]>>();\n for (const entry of entries) {\n let byField = groups.get(entry.objectRef);\n if (!byField) {\n byField = new Map<string, FieldUsageReportEntry[]>();\n groups.set(entry.objectRef, byField);\n }\n const samples = byField.get(entry.fieldName);\n if (samples) {\n samples.push(entry);\n } else {\n byField.set(entry.fieldName, [entry]);\n }\n }\n\n let accepted = 0;\n let dropped = 0;\n\n for (const [objectRef, byField] of groups) {\n let fieldMap: FieldDefinitionMap;\n try {\n fieldMap = await getObjectFieldMap(objectRef);\n } catch {\n for (const samples of byField.values()) {\n dropped += samples.length;\n }\n continue;\n }\n\n const resolvedFields = await this.resolveDefaultsForCaller(\n objectRef,\n tenantId,\n userId,\n );\n if (!resolvedFields) {\n // Defaults are the deviation authority; without them every entry\n // would have to be guessed. Drop the group rather than guess.\n for (const samples of byField.values()) {\n dropped += samples.length;\n }\n continue;\n }\n\n for (const [fieldName, samples] of byField) {\n const fieldDef = fieldMap.get(fieldName);\n if (!fieldDef || !isUsageAddressableField(fieldDef)) {\n dropped += samples.length;\n continue;\n }\n\n // The durable receipt is the rate rail: at most ONE sample from this\n // user can influence this field's evidence per UTC day, even across\n // repeated requests or concurrent app replicas. Claim it only after\n // registry validation, so a stale/unknown field cannot burn a valid\n // field's daily allowance. Repeated reports are intentionally ignored\n // (not reported as `dropped`, which is reserved for stale entries).\n if (\n !(await receipts.claim({\n tenantId,\n userId,\n objectRef,\n fieldName,\n period,\n }))\n ) {\n continue;\n }\n\n // Batches can carry more than one value for a field, but the same\n // daily rule applies within one request too. The first sample is the\n // one durable contribution for this member/day/field.\n const sample = samples[0];\n\n const countOnly =\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined;\n const histogramEligible =\n !countOnly && isHistogramEligibleField(fieldDef);\n const resolved = resolvedFields[fieldName];\n const hasDefault = resolved?.hasDefault === true;\n const defaultValue = hasDefault ? resolved?.defaultValue : undefined;\n\n let deviations = 0;\n const histogramKeys: string[] = [];\n if ('value' in sample) {\n // Server-derived deviation: the resolved default is the authority.\n if (!hasDefault || !usageValuesEqual(sample.value, defaultValue)) {\n deviations += 1;\n }\n if (histogramEligible) {\n const key = serializeHistogramSample(fieldDef, sample.value);\n if (key !== null) {\n histogramKeys.push(key);\n }\n }\n } else if (sample.matchedDefault !== true) {\n // Value-less entry: only the deviation bit is taken from the\n // client hint (see the bounded-trust note above).\n deviations += 1;\n }\n\n await this.mergeIntoBucket({\n objectRef,\n fieldName,\n tenantId,\n period,\n userId,\n submissionCount: 1,\n deviationCount: deviations,\n histogramKeys,\n });\n accepted += 1;\n }\n }\n\n return { accepted, dropped };\n }\n\n /**\n * The defaults the CALLER's forms would have prefilled — the deviation\n * authority. Resolved for the ambient `(tenant, user)` identity (the\n * resolver's own TTL cache keeps repeat batches cheap).\n *\n * Ingestion is fire-and-forget, so a stored-layer read failure degrades to\n * the CODE-SEED-only resolution (no db) rather than failing the request;\n * `null` (both attempts failed) makes the caller drop the group.\n */\n private async resolveDefaultsForCaller(\n objectRef: string,\n tenantId: string,\n userId: string,\n ): Promise<Record<string, ResolvedFieldPolicy> | undefined | null> {\n // Dynamic import mirrors the resolver seams elsewhere in this package\n // (the resolver statically imports the policy collection).\n const { resolveFieldPolicy } = await import('../field-policy-resolver.js');\n try {\n const resolved = await resolveFieldPolicy(objectRef, {\n tenantId,\n userId,\n db: this.db,\n });\n return resolved.fields;\n } catch {\n try {\n const seedOnly = await resolveFieldPolicy(objectRef, {\n tenantId,\n userId,\n });\n return seedOnly.fields;\n } catch {\n return null;\n }\n }\n }\n\n /**\n * Counter rows within a period window (inclusive bounds, lexicographic ISO\n * day comparison), optionally restricted to one tenant — the read the\n * suggestion-generation job consumes.\n */\n async listWindow(options: {\n fromPeriod: string;\n toPeriod?: string;\n tenantId?: string | null;\n }): Promise<FieldUsageCounter[]> {\n const where: Record<string, unknown> = {\n 'period >=': options.fromPeriod,\n };\n if (options.toPeriod) {\n where['period <='] = options.toPeriod;\n }\n if (options.tenantId) {\n where.tenantId = options.tenantId;\n }\n return this.list({ where, orderBy: 'period ASC' });\n }\n\n /**\n * Idempotent read-modify-write merge into the deterministic day bucket.\n * Counters are approximate by design: concurrent cross-process merges may\n * lose an increment (documented on the model), while the deterministic id\n * plus the natural-key unique index keep concurrent creates converging on\n * one row.\n */\n private async mergeIntoBucket(options: {\n objectRef: string;\n fieldName: string;\n tenantId: string;\n period: string;\n userId: string;\n /** Every observed submission (the dominance denominator). */\n submissionCount: number;\n /** Submissions that differed from the resolved default. */\n deviationCount: number;\n histogramKeys: string[];\n }): Promise<void> {\n const id = await fieldUsageCounterId(\n options.tenantId,\n options.objectRef,\n options.fieldName,\n options.period,\n );\n\n const existing = await this.get(id);\n const counter =\n existing ??\n new FieldUsageCounter({\n db: this.db,\n id,\n objectRef: options.objectRef,\n fieldName: options.fieldName,\n tenantId: options.tenantId,\n period: options.period,\n });\n\n counter.submissionCount += options.submissionCount;\n counter.setCount += options.deviationCount;\n // Distinct users track the PROMOTE signal: only a caller who actually set\n // a non-default value counts as \"actively filling this field in\".\n if (options.deviationCount > 0) {\n counter.addDistinctUser(options.userId);\n }\n for (const key of options.histogramKeys) {\n counter.recordHistogramSample(key);\n }\n if (!existing) {\n await counter.initialize();\n }\n await counter.save();\n }\n}\n\n/** Internal only — this table has no generated surface. */\n@smrt({\n conflictColumns: [\n 'tenant_id',\n 'user_id',\n 'object_ref',\n 'field_name',\n 'period',\n ],\n api: false,\n cli: false,\n mcp: false,\n})\nclass FieldUsageReportReceiptCollection extends SmrtCollection<FieldUsageReportReceipt> {\n static readonly _itemClass = FieldUsageReportReceipt;\n\n async claim(options: {\n tenantId: string;\n userId: string;\n objectRef: string;\n fieldName: string;\n period: string;\n }): Promise<boolean> {\n const id = await fieldUsageReportReceiptId(\n options.tenantId,\n options.userId,\n options.objectRef,\n options.fieldName,\n options.period,\n );\n if (await this.get(id)) return false;\n try {\n await this.create({ ...options, id, _insertOnly: true });\n return true;\n } catch (error) {\n // Only a receipt that now exists proves another request won the race;\n // validation/schema/connection failures must remain visible.\n if (await this.get(id)) return false;\n throw error;\n }\n }\n}\n\n/**\n * Deterministic bucket row id (the TenantUsageMetric `recordUsage` precedent):\n * SHA-256 over the natural key, formatted as a v5-style UUID so the id column\n * stays native UUID on PostgreSQL/DuckDB.\n */\nexport async function fieldUsageCounterId(\n tenantId: string,\n objectRef: string,\n fieldName: string,\n period: string,\n): Promise<string> {\n return deterministicFieldsUuid([\n 'field-usage-counter',\n tenantId,\n objectRef,\n fieldName,\n period,\n ]);\n}\n\n/** Deterministic receipt id for the durable once-per-day contribution rule. */\nexport async function fieldUsageReportReceiptId(\n tenantId: string,\n userId: string,\n objectRef: string,\n fieldName: string,\n period: string,\n): Promise<string> {\n return deterministicFieldsUuid([\n 'field-usage-report-receipt',\n tenantId,\n userId,\n objectRef,\n fieldName,\n period,\n ]);\n}\n\n/** Mirrors the resolver's policy-addressable exclusions, plus transient. */\nfunction isUsageAddressableField(field: RegisteredFieldInfo): boolean {\n if (field._meta?.__smrtSystemField === true) {\n return false;\n }\n if (\n field.type === 'oneToMany' ||\n field.type === 'manyToMany' ||\n field.type === 'meta'\n ) {\n return false;\n }\n return !isTransientField(field);\n}\n\n/**\n * Histogram eligibility (#2051 pin): ONLY low-cardinality field types —\n * `boolean` and reference ids (`foreignKey` / `crossPackageRef`). Free text\n * is NEVER histogrammed, even when non-sensitive (PII risk); numeric,\n * datetime, and json fields are count-only too.\n */\nexport function isHistogramEligibleField(field: RegisteredFieldInfo): boolean {\n return (\n field.type === 'boolean' ||\n field.type === 'foreignKey' ||\n field.type === 'crossPackageRef'\n );\n}\n\n/**\n * Serialize one sample into a histogram key, type-checked against the field:\n * booleans must be real booleans (`'true'` / `'false'` keys); reference ids\n * must be STORABLE ids for the field — `isStorableReferenceId` applies the same\n * native-UUID / `idType: 'text'` rule stored defaults are held to, within the\n * bounded key length. Anything else is skipped (the submission still counts —\n * count-only for that sample).\n *\n * The reference check is load-bearing, not cosmetic: recording an unstorable\n * id would let an authenticated caller poison a histogram, win dominance with\n * it, and produce a `default` suggestion whose write is then rejected by the\n * same rule — turning a bad sample into a stuck generation candidate.\n */\nexport function serializeHistogramSample(\n field: RegisteredFieldInfo,\n value: unknown,\n): string | null {\n if (field.type === 'boolean') {\n return typeof value === 'boolean' ? String(value) : null;\n }\n if (\n isStorableReferenceId(field, value) &&\n value.length <= MAX_VALUE_HISTOGRAM_KEY_LENGTH\n ) {\n return value;\n }\n return null;\n}\n\n/** Decode a histogram key back into the typed value it was recorded from. */\nexport function decodeHistogramKey(\n field: RegisteredFieldInfo,\n key: string,\n): unknown {\n if (field.type === 'boolean') {\n return key === 'true';\n }\n return key;\n}\n\nfunction validateEntriesInput(\n rawEntries: FieldUsageReportEntry[] | undefined,\n): FieldUsageReportEntry[] {\n if (!Array.isArray(rawEntries) || rawEntries.length === 0) {\n throw new Error(\n 'reportUsage requires a non-empty \"entries\" array of ' +\n '{ objectRef, fieldName, value?, matchedDefault? } samples',\n );\n }\n if (rawEntries.length > MAX_USAGE_REPORT_ENTRIES) {\n throw new Error(\n `reportUsage accepts at most ${MAX_USAGE_REPORT_ENTRIES} entries ` +\n `per call (got ${rawEntries.length})`,\n );\n }\n for (const entry of rawEntries) {\n if (\n !entry ||\n typeof entry !== 'object' ||\n typeof entry.objectRef !== 'string' ||\n entry.objectRef.trim() === '' ||\n typeof entry.fieldName !== 'string' ||\n entry.fieldName.trim() === ''\n ) {\n throw new Error(\n 'reportUsage entries must carry non-empty objectRef and fieldName strings',\n );\n }\n }\n return rawEntries;\n}\n","import type {\n DataSurfaceActionDescriptor,\n DataSurfaceColumnCapability,\n DataSurfaceColumnDescriptor,\n DataSurfaceColumnOperators,\n DataSurfaceColumnRole,\n DataSurfaceDescriptor,\n} from '@happyvertical/smrt-ui/data';\nimport type {\n ResolvedFieldPolicy,\n ResolvedObjectFieldPolicy,\n} from './types.js';\n\n/** Optional host metadata for columns that do not come from a model field. */\nexport interface FieldPolicyDataSurfaceOptions {\n /** Map domain column ids to manifest field names without changing column ids. */\n fieldNameByColumnId?: Readonly<Record<string, string>>;\n /** Static visibility is a host constraint and can only be narrowed. */\n staticHiddenColumnIds?: readonly string[];\n /** Structural roles are always retained when policy filters data columns. */\n roleByColumnId?: Readonly<Record<string, DataSurfaceColumnRole>>;\n /** Explicit host authorization for otherwise restricted column ids. */\n authorizedColumnIds?: readonly string[];\n}\n\nconst POLICY_NARROWED_CAPABILITIES = new Set<DataSurfaceColumnCapability>([\n 'read',\n 'search',\n 'filter',\n 'sort',\n 'project',\n]);\n\nfunction policyFieldName(\n column: DataSurfaceColumnDescriptor,\n options: FieldPolicyDataSurfaceOptions,\n): string {\n return (\n column.fieldName ?? options.fieldNameByColumnId?.[column.id] ?? column.id\n );\n}\n\nfunction roleForColumn(\n column: DataSurfaceColumnDescriptor,\n options: FieldPolicyDataSurfaceOptions,\n): DataSurfaceColumnRole | undefined {\n return column.role ?? options.roleByColumnId?.[column.id];\n}\n\nfunction isSensitiveColumn(column: DataSurfaceColumnDescriptor): boolean {\n return column.sensitivity === 'sensitive' || column.sensitivity === 'secret';\n}\n\nfunction isAuthorizedColumn(\n column: DataSurfaceColumnDescriptor,\n options: FieldPolicyDataSurfaceOptions,\n): boolean {\n return options.authorizedColumnIds?.includes(column.id) === true;\n}\n\nfunction isStructuralColumn(\n column: DataSurfaceColumnDescriptor,\n options: FieldPolicyDataSurfaceOptions,\n): boolean {\n const role = roleForColumn(column, options);\n return (\n role === 'computed' ||\n role === 'row-key' ||\n role === 'selection' ||\n role === 'action'\n );\n}\n\nfunction narrowOperators(\n operators: DataSurfaceColumnOperators | undefined,\n hidden: boolean,\n): DataSurfaceColumnOperators | undefined {\n if (!operators) return undefined;\n if (hidden) return {};\n return {\n ...(operators.search ? { search: [...operators.search] } : {}),\n ...(operators.filter ? { filter: [...operators.filter] } : {}),\n ...(operators.sort ? { sort: [...operators.sort] } : {}),\n };\n}\n\nfunction narrowCapabilities(\n capabilities: readonly DataSurfaceColumnCapability[],\n hidden: boolean,\n): DataSurfaceColumnCapability[] {\n if (hidden) return [];\n return capabilities.filter((capability) =>\n POLICY_NARROWED_CAPABILITIES.has(capability),\n );\n}\n\nfunction policyColumn(\n column: DataSurfaceColumnDescriptor,\n policy: ResolvedFieldPolicy | undefined,\n hiddenByStaticPolicy: boolean,\n structural: boolean,\n restricted: boolean,\n): DataSurfaceColumnDescriptor {\n const hidden = hiddenByStaticPolicy || policy?.visibility === 'hidden';\n\n // Computed, selection, action, and row-key columns have no manifest field\n // and are intentionally copied through unchanged when unrestricted. Hidden\n // structural columns still must not remain discoverable or executable.\n if (structural) {\n if (hidden || restricted) {\n return {\n ...column,\n visibility: 'hidden',\n readable: false,\n capabilities: [],\n operators: {},\n searchOperators: [],\n filterOperators: [],\n sortOperators: [],\n };\n }\n const explicitlyAuthorized =\n !restricted && (column.readable === false || isSensitiveColumn(column));\n return {\n ...column,\n ...(explicitlyAuthorized ? { readable: true } : {}),\n };\n }\n\n if (!policy) {\n const unreadable = hiddenByStaticPolicy || restricted;\n const explicitlyAuthorized = column.readable === false && !restricted;\n return {\n ...column,\n ...(unreadable ? { visibility: 'hidden' as const } : {}),\n ...(column.readable === undefined &&\n (column.sensitivity === 'sensitive' || column.sensitivity === 'secret')\n ? { readable: true }\n : {}),\n ...(explicitlyAuthorized ? { readable: true } : {}),\n ...(unreadable\n ? {\n readable: false,\n capabilities: narrowCapabilities(column.capabilities, true),\n ...(column.operators ? { operators: {} } : {}),\n ...(column.searchOperators ? { searchOperators: [] } : {}),\n ...(column.filterOperators ? { filterOperators: [] } : {}),\n ...(column.sortOperators ? { sortOperators: [] } : {}),\n }\n : {}),\n };\n }\n\n const unreadable = hidden || restricted;\n const label = policy.label ?? column.label;\n const description = policy.help ?? column.description;\n return {\n ...column,\n label,\n ...(description ? { description } : {}),\n ...(policy.order === null ? {} : { order: policy.order }),\n visibility: policy.visibility,\n readable: !unreadable,\n capabilities: narrowCapabilities(column.capabilities, unreadable),\n ...(narrowOperators(column.operators, unreadable)\n ? { operators: narrowOperators(column.operators, unreadable) }\n : {}),\n ...(column.searchOperators\n ? { searchOperators: unreadable ? [] : [...column.searchOperators] }\n : {}),\n ...(column.filterOperators\n ? { filterOperators: unreadable ? [] : [...column.filterOperators] }\n : {}),\n ...(column.sortOperators\n ? { sortOperators: unreadable ? [] : [...column.sortOperators] }\n : {}),\n ...(unreadable ? { visibility: 'hidden' as const } : {}),\n };\n}\n\nfunction actionUsesHiddenColumn(\n action: DataSurfaceActionDescriptor,\n hiddenColumnIds: ReadonlySet<string>,\n): boolean {\n return (\n action.columnIds?.some((columnId) => hiddenColumnIds.has(columnId)) ?? false\n );\n}\n\n/**\n * Apply an effective field policy to a mounted DataSurface descriptor.\n *\n * This adapter is deliberately outside smrt-ui: the UI contract remains\n * domain-neutral while fields owns the policy-to-surface semantics. Static\n * host constraints and policy restrictions only remove capabilities; they\n * never reveal a field or rename a domain column id.\n */\nexport function policyToDataSurfaceDescriptor(\n policy: ResolvedObjectFieldPolicy,\n descriptor: DataSurfaceDescriptor,\n options: FieldPolicyDataSurfaceOptions = {},\n): DataSurfaceDescriptor {\n const staticHidden = new Set(options.staticHiddenColumnIds ?? []);\n const rowKey = descriptor.rowKey;\n const mapped = descriptor.columns.map((column, index) => {\n const field = policy.fields[policyFieldName(column, options)];\n const structural = isStructuralColumn(column, options);\n const restricted =\n (column.readable === false || isSensitiveColumn(column)) &&\n !isAuthorizedColumn(column, options);\n const rowKeyHidden =\n column.id === rowKey &&\n (staticHidden.has(column.id) ||\n column.visibility === 'hidden' ||\n field?.visibility === 'hidden');\n const effective = policyColumn(\n column,\n field,\n staticHidden.has(column.id) || column.visibility === 'hidden',\n structural,\n restricted,\n );\n return { column, effective, field, structural, index, rowKeyHidden };\n });\n\n // A hidden data field must not be describable or restorable. The row key is\n // the one technical exception: mounted surfaces must retain stable identity,\n // but it has no read/query capabilities when policy-hidden.\n const hiddenColumnIds = new Set<string>();\n const columns = mapped\n .filter(({ column, effective }) => {\n const hidden =\n staticHidden.has(column.id) ||\n column.visibility === 'hidden' ||\n effective.visibility === 'hidden';\n if (hidden && column.id !== rowKey) hiddenColumnIds.add(column.id);\n return !hidden || column.id === rowKey;\n })\n .sort((left, right) => {\n if (left.column.id === rowKey) return -1;\n if (right.column.id === rowKey) return 1;\n const leftOrder = left.effective.order ?? Number.POSITIVE_INFINITY;\n const rightOrder = right.effective.order ?? Number.POSITIVE_INFINITY;\n return leftOrder - rightOrder || left.index - right.index;\n })\n .map(({ effective, column, rowKeyHidden }) =>\n column.id === rowKey && (hiddenColumnIds.has(column.id) || rowKeyHidden)\n ? {\n ...effective,\n visibility: 'hidden' as const,\n readable: false,\n capabilities: [],\n operators: {},\n searchOperators: [],\n filterOperators: [],\n sortOperators: [],\n }\n : effective,\n );\n\n const visibleIds = new Set(columns.map((column) => column.id));\n const hasCapability = (\n columnId: string,\n capability: DataSurfaceColumnCapability,\n ): boolean => {\n const column = columns.find((candidate) => candidate.id === columnId);\n return column?.capabilities.includes(capability) ?? false;\n };\n const allowlist = (\n ids: readonly string[] | undefined,\n capability: DataSurfaceColumnCapability,\n ): string[] | undefined =>\n ids\n ? ids.filter(\n (columnId) =>\n visibleIds.has(columnId) && hasCapability(columnId, capability),\n )\n : undefined;\n\n const actions = descriptor.actions\n .filter((action) => !actionUsesHiddenColumn(action, hiddenColumnIds))\n .map((action) => ({\n ...action,\n ...(action.columnIds\n ? {\n columnIds: action.columnIds.filter((columnId) =>\n visibleIds.has(columnId),\n ),\n }\n : {}),\n }));\n\n return {\n ...descriptor,\n columns,\n query: {\n ...descriptor.query,\n projectableColumnIds:\n allowlist(descriptor.query.projectableColumnIds, 'project') ?? [],\n ...(allowlist(descriptor.query.searchableColumnIds, 'search')\n ? {\n searchableColumnIds: allowlist(\n descriptor.query.searchableColumnIds,\n 'search',\n ),\n }\n : descriptor.query.searchableColumnIds\n ? { searchableColumnIds: [] }\n : {}),\n ...(allowlist(descriptor.query.filterableColumnIds, 'filter')\n ? {\n filterableColumnIds: allowlist(\n descriptor.query.filterableColumnIds,\n 'filter',\n ),\n }\n : descriptor.query.filterableColumnIds\n ? { filterableColumnIds: [] }\n : {}),\n ...(allowlist(descriptor.query.sortableColumnIds, 'sort')\n ? {\n sortableColumnIds: allowlist(\n descriptor.query.sortableColumnIds,\n 'sort',\n ),\n }\n : descriptor.query.sortableColumnIds\n ? { sortableColumnIds: [] }\n : {}),\n },\n actions,\n };\n}\n\n/** Descriptive alias for hosts that treat policy application as a transform. */\nexport const applyFieldPolicyToDataSurface = policyToDataSurfaceDescriptor;\n","import { importWorkspaceModule } from '@happyvertical/smrt-core/utils/import-workspace-module';\nimport {\n assertTenantReadAllowed,\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport { getCachedFieldPolicy, setCachedFieldPolicy } from './cache.js';\nimport { FieldPolicyCollection } from './collections/FieldPolicyCollection.js';\nimport {\n buildCodeSeedDelta,\n buildCodeSeedVisibility,\n type FieldDefinitionMap,\n getCodeSeedGroup,\n getObjectFieldMap,\n isPolicyAddressableField,\n isRequiredField,\n isUsableRequiredDefault,\n} from './field-definitions.js';\nimport type { FieldPolicy } from './models/FieldPolicy.js';\nimport type {\n ExplainedObjectFieldPolicy,\n FieldPolicyDelta,\n FieldPolicyLayerContribution,\n FieldPolicyTenantHierarchyProvider,\n FieldPolicyTenantNode,\n FieldPolicyUsersModule,\n FieldPolicyVisibility,\n ResolvedFieldPolicy,\n ResolvedObjectFieldPolicy,\n ResolveFieldPolicyOptions,\n SmrtClassOptions,\n} from './types.js';\n\n/** Accumulated merge state for one field while layers apply. */\ninterface MergedPolicyState {\n default?: { value: unknown };\n visibility: FieldPolicyVisibility;\n help?: string;\n label?: string;\n order?: number;\n locked?: boolean;\n}\n\n/**\n * Resolve the merged field policy for `objectRef` in the given\n * `(tenantId, userId)` context: code seed → app rows → tenant rows (hierarchy\n * walk root → leaf) → user rows. Defaults AND visibility both resolve through\n * the user tier.\n *\n * Results are cached per `(database, objectRef, tenantId, userId)` with a\n * short TTL; `FieldPolicy.save()`/`.delete()` invalidate the object's entries.\n */\nexport async function resolveFieldPolicy(\n objectRef: string,\n options: ResolveFieldPolicyOptions = {},\n): Promise<ResolvedObjectFieldPolicy> {\n const explained = await resolveFieldPolicyExplained(objectRef, options);\n return { objectRef: explained.objectRef, fields: explained.fields };\n}\n\n/**\n * The tenant ids whose rows participate in precedence for a tenant, root to\n * leaf. Catalog code uses this instead of reimplementing inheritance-break\n * handling when it decides whether an inherited override is customized.\n */\nexport async function resolveSurvivingTenantChainIds(\n tenantId: string,\n options: ResolveFieldPolicyOptions = {},\n): Promise<string[]> {\n assertResolutionAllowedInContext(tenantId, null);\n const chain = await resolveTenantChain(tenantId, options);\n return selectSurvivingChainSuffix(chain).map((node) => node.id);\n}\n\n/**\n * Explain variant: the merged result plus ordered per-layer contributions for\n * each field, so the gear UI (#2049, \"shows inherited base\") and the control\n * panel (#2050, effective-value-per-layer) never re-derive precedence.\n *\n * A user-layer row suppressed by an effective org lock is omitted from the\n * layer list too — the listed layers always reproduce the merged result.\n */\nexport async function resolveFieldPolicyExplained(\n objectRef: string,\n options: ResolveFieldPolicyOptions = {},\n): Promise<ExplainedObjectFieldPolicy> {\n const tenantId = options.tenantId ?? null;\n const userId = options.userId ?? null;\n\n assertResolutionAllowedInContext(tenantId, userId);\n\n let collection: FieldPolicyCollection | null = null;\n let cacheDb: unknown = options.db;\n if (options.db) {\n collection = await FieldPolicyCollection.create({ db: options.db });\n cacheDb = collection.db;\n }\n\n // The field map load also validates objectRef against the live registry, so\n // unknown refs throw before the cache is consulted.\n const fieldMap = await getObjectFieldMap(objectRef);\n\n const hasExcludedRows = (options.excludePolicyIds?.size ?? 0) > 0;\n if (!hasExcludedRows) {\n const cached = getCachedFieldPolicy(\n objectRef,\n tenantId,\n userId,\n cacheDb,\n options.tenantHierarchyLoader,\n );\n if (cached) {\n return cached;\n }\n }\n\n const policyFields = selectPolicyAddressableFields(fieldMap);\n const codeVisibility = buildCodeSeedVisibility(fieldMap);\n\n const appRows = collection\n ? await collection.getAppRows(objectRef)\n : new Map<string, FieldPolicy>();\n\n let survivingChain: FieldPolicyTenantNode[] = [];\n let tenantRows = new Map<string, Map<string, FieldPolicy>>();\n if (collection && tenantId) {\n const chain = await resolveTenantChain(tenantId, options);\n // Permission-inheritance breaks are chain-STRUCTURAL (node flags, not\n // rows), so a break at node i discards every earlier tenant contribution\n // for ALL fields — the merge baseline resets to the app-layer state\n // there. Only the suffix from the LAST break participates in merging and\n // in the explained layers, so sequentially replaying the listed deltas\n // always reproduces the merged result.\n survivingChain = selectSurvivingChainSuffix(chain);\n tenantRows = await collection.getTenantRows(\n objectRef,\n survivingChain.map((node) => node.id),\n );\n }\n\n const userRows =\n collection && userId\n ? await collection.getUserRows(objectRef, userId)\n : new Map<string, FieldPolicy>();\n\n const fields: Record<string, ResolvedFieldPolicy> = {};\n const layers: Record<string, FieldPolicyLayerContribution[]> = {};\n\n for (const [fieldName, fieldDef] of policyFields) {\n const contributions: FieldPolicyLayerContribution[] = [];\n\n const codeDelta = buildCodeSeedDelta(\n fieldDef,\n codeVisibility.get(fieldName) ?? 'basic',\n );\n contributions.push({ layer: 'code', delta: codeDelta });\n\n let state: MergedPolicyState = applyDelta(\n { visibility: 'basic' },\n codeDelta,\n );\n\n const appRow = appRows.get(fieldName);\n if (appRow && !options.excludePolicyIds?.has(String(appRow.id))) {\n const delta = rowToDelta(appRow);\n contributions.push({ layer: 'app', delta });\n state = applyDelta(state, delta);\n }\n\n // Tenant chain walk, root → leaf, over the surviving suffix only (nodes\n // before the last permission-inheritance break contribute nothing — see\n // selectSurvivingChainSuffix). Equivalent to smrt-features' baseline\n // walk, but the explained contributions never list discarded ancestors.\n for (const node of survivingChain) {\n const row = tenantRows.get(node.id)?.get(fieldName);\n if (row && !options.excludePolicyIds?.has(String(row.id))) {\n const delta = rowToDelta(row);\n contributions.push({ layer: 'tenant', tenantId: node.id, delta });\n state = applyDelta(state, delta);\n }\n }\n\n // Org lock: when the code/app/tenant tiers resolve locked, the user tier\n // is skipped entirely — a stale user row cannot bypass a later lock.\n const orgLocked = state.locked === true;\n const userRow = userId ? userRows.get(fieldName) : undefined;\n if (\n userRow &&\n !options.excludePolicyIds?.has(String(userRow.id)) &&\n !orgLocked\n ) {\n const delta = rowToDelta(userRow);\n contributions.push({ layer: 'user', userId: userId as string, delta });\n state = applyDelta(state, delta);\n }\n\n // Resolver-side required-field safety net: a required field with no\n // usable resolved default is ALWAYS visible, regardless of stored\n // visibility — write-time enforcement alone breaks when a DIFFERENT row's\n // deletion removes the default a demotion relied on.\n const required = isRequiredField(fieldDef);\n let visibilityForced = false;\n if (\n required &&\n !isUsableRequiredDefault(state.default) &&\n state.visibility !== 'basic'\n ) {\n state = { ...state, visibility: 'basic' };\n visibilityForced = true;\n }\n\n fields[fieldName] = {\n fieldName,\n hasDefault: state.default !== undefined,\n defaultValue: state.default?.value,\n visibility: state.visibility,\n help: state.help ?? null,\n label: state.label ?? null,\n order: state.order ?? null,\n group: getCodeSeedGroup(fieldDef),\n locked: state.locked === true,\n required,\n ...(visibilityForced ? { visibilityForced: true } : {}),\n };\n layers[fieldName] = contributions;\n }\n\n const explained: ExplainedObjectFieldPolicy = { objectRef, fields, layers };\n if (!hasExcludedRows) {\n setCachedFieldPolicy(\n objectRef,\n tenantId,\n userId,\n cacheDb,\n explained,\n options.tenantHierarchyLoader,\n );\n }\n return explained;\n}\n\n/**\n * Fail-closed isolation guard: an active non-bypass tenant context may only\n * resolve its own tenant and its own user. App-only resolution (`tenantId`\n * null) is always allowed — app rows are global data.\n *\n * Mirrors the write-side rule in `FieldPolicy`: a MISSING identity component\n * denies, it never skips. A context that carries permissions but no user id\n * (no `resolveUserId` hook configured — API-key auth, service principals,\n * background jobs) must not be able to read any user's resolved policy.\n * Context-LESS callers stay allowed: `resolveFieldPolicy` is a trusted\n * server-side API, and the public `resolveBatch` route never lets a request\n * body select a user — it takes identity from the ambient context alone.\n */\nfunction assertResolutionAllowedInContext(\n tenantId: string | null,\n userId: string | null,\n): void {\n if (tenantId) {\n assertTenantReadAllowed(tenantId, 'resolveFieldPolicy');\n }\n\n if (!userId) {\n return;\n }\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return;\n }\n if (context.userId === undefined) {\n throw new TenantIsolationError(\n `Tenant isolation violation in resolveFieldPolicy: the ambient ` +\n `context carries no user id, so user-scope resolution for ` +\n `'${userId}' is not attributable`,\n { tenantId: context.tenantId },\n );\n }\n if (context.userId !== userId) {\n throw new TenantIsolationError(\n `Tenant isolation violation in resolveFieldPolicy: context user is ` +\n `'${context.userId}' but resolution requested '${userId}'`,\n { tenantId: context.tenantId },\n );\n }\n}\n\n/**\n * Fields that participate in policy resolution: everything except injected\n * framework system fields, relationship pseudo-fields, and STI meta\n * internals (matching the exclusions of the generated web field definitions).\n */\nfunction selectPolicyAddressableFields(\n fieldMap: FieldDefinitionMap,\n): FieldDefinitionMap {\n const selected: FieldDefinitionMap = new Map();\n for (const [name, field] of fieldMap) {\n if (isPolicyAddressableField(field)) {\n selected.set(name, field);\n }\n }\n return selected;\n}\n\n/** A stored row's sparse contribution (NULL columns contribute nothing). */\nfunction rowToDelta(row: FieldPolicy): FieldPolicyDelta {\n const delta: FieldPolicyDelta = {};\n\n if (row.defaultValue !== null && row.defaultValue !== undefined) {\n try {\n delta.default = { value: JSON.parse(row.defaultValue) };\n } catch {\n // Unparseable stored JSON (should be prevented by save-time validation)\n // contributes nothing rather than poisoning resolution.\n }\n }\n if (row.visibility !== null && row.visibility !== undefined) {\n delta.visibility = row.visibility;\n }\n if (row.help !== null && row.help !== undefined) {\n delta.help = row.help;\n }\n if (row.label !== null && row.label !== undefined) {\n delta.label = row.label;\n }\n if (row.displayOrder !== null && row.displayOrder !== undefined) {\n delta.order = row.displayOrder;\n }\n if (row.locked !== null && row.locked !== undefined) {\n delta.locked = row.locked;\n }\n\n return delta;\n}\n\nfunction applyDelta(\n state: MergedPolicyState,\n delta: FieldPolicyDelta,\n): MergedPolicyState {\n return {\n default: delta.default ?? state.default,\n visibility: delta.visibility ?? state.visibility,\n help: delta.help ?? state.help,\n label: delta.label ?? state.label,\n order: delta.order ?? state.order,\n locked: delta.locked ?? state.locked,\n };\n}\n\n/**\n * The chain suffix that actually participates in merging: nodes from the\n * LAST permission-inheritance break onward (a node breaks inheritance when\n * its parent does not cascade permissions or it does not accept them —\n * smrt-features semantics). Everything before the last break is discarded\n * for every field, so it is excluded from both merging and the explained\n * layer contributions.\n */\nfunction selectSurvivingChainSuffix(\n chain: FieldPolicyTenantNode[],\n): FieldPolicyTenantNode[] {\n let survivingStart = 0;\n for (let index = 1; index < chain.length; index++) {\n const inherits =\n chain[index - 1].cascadePermissions && chain[index].inheritPermissions;\n if (!inherits) {\n survivingStart = index;\n }\n }\n return chain.slice(survivingStart);\n}\n\nasync function resolveTenantChain(\n tenantId: string,\n options: ResolveFieldPolicyOptions,\n): Promise<FieldPolicyTenantNode[]> {\n const loader = options.tenantHierarchyLoader || defaultTenantHierarchyLoader;\n const provider = await loader({ db: options.db } as SmrtClassOptions);\n\n if (provider) {\n const chain = await provider.getChain(tenantId);\n if (chain.length > 0) {\n return chain;\n }\n }\n\n // Flat-tenant fallback (no hierarchy provider, or the provider does not\n // know the tenant): treat the tenant as a single-node chain.\n return [{ id: tenantId, inheritPermissions: true, cascadePermissions: true }];\n}\n\n/**\n * Default hierarchy loader: dynamic-imports `@happyvertical/smrt-users` (the\n * smrt-features precedent — a loader function, not a container registration)\n * and returns `null` when it is not installed so resolution degrades to the\n * flat-tenant fallback.\n */\nasync function defaultTenantHierarchyLoader(\n options: SmrtClassOptions,\n): Promise<FieldPolicyTenantHierarchyProvider | null> {\n try {\n const usersModule = await importWorkspaceModule<FieldPolicyUsersModule>({\n packageName: '@happyvertical/smrt-users',\n sourceEntry: 'packages/users/src/collections/index.ts',\n purpose: 'tenant-aware field policy resolution',\n });\n\n const tenantCollection = await usersModule.TenantCollection.create(options);\n return {\n async getChain(tenantId: string): Promise<FieldPolicyTenantNode[]> {\n const tenant = await tenantCollection.get({ id: tenantId });\n if (!tenant) {\n return [];\n }\n\n const ancestors = await tenantCollection.getAncestorsFromRoot(tenantId);\n return [...ancestors, tenant].map((node) => ({\n id: String(node.id),\n inheritPermissions: Boolean(node.inheritPermissions),\n cascadePermissions: Boolean(node.cascadePermissions),\n }));\n },\n };\n } catch (error) {\n if (isMissingUsersDependency(error)) {\n return null;\n }\n throw error;\n }\n}\n\n/** Node's missing-module message shapes, capturing the quoted specifier. */\nconst MISSING_MODULE_TARGET_PATTERN =\n /Cannot find (?:package|module) '([^']+)'/;\n\n/** Whether a missing-module TARGET specifier is smrt-users (or a subpath). */\nfunction isUsersSpecifier(target: string): boolean {\n return (\n target === '@happyvertical/smrt-users' ||\n target.startsWith('@happyvertical/smrt-users/')\n );\n}\n\n/**\n * Whether an import failure means `@happyvertical/smrt-users` is simply not\n * installed (→ flat-tenant fallback) rather than installed-but-broken\n * (→ rethrow, surfacing the problem instead of silently losing ancestor\n * locks/defaults).\n *\n * The decision is made on the missing-module TARGET parsed from Node's\n * `Cannot find package/module '<specifier>'` message (walking the full\n * `cause` chain): only a target that IS smrt-users (or one of its subpaths)\n * counts. A transitive failure INSIDE an installed smrt-users names the\n * other package as the target — with the users path merely appearing as the\n * importer — and therefore rethrows. `importWorkspaceModule`'s own\n * source-fallback wrapper (\"Failed to load @happyvertical/smrt-users for\n * ...\") is also accepted: it is thrown only when the users package itself\n * cannot be located.\n *\n * Exported for direct testing; not re-exported from the package index.\n */\nexport function isMissingUsersDependency(error: unknown): boolean {\n let current: unknown = error;\n const seen = new Set<unknown>();\n\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current);\n\n const match = current.message.match(MISSING_MODULE_TARGET_PATTERN);\n if (match && isUsersSpecifier(match[1])) {\n return true;\n }\n\n if (\n current.message.includes('Failed to load @happyvertical/smrt-users for')\n ) {\n return true;\n }\n\n current = current.cause;\n }\n\n return false;\n}\n","/**\n * Server-side data builder for the field-policy AdminShell destination.\n *\n * This deliberately mirrors the `@happyvertical/smrt-svelte/settings`\n * contract structurally. Fields does not depend on smrt-svelte, and only the\n * selected object carries its browser-safe field definitions.\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport {\n FieldPolicyCollection,\n MAX_FIELD_POLICY_AUDIT_OBJECT_REFS,\n} from './collections/FieldPolicyCollection.js';\nimport {\n getFieldReadPermission,\n getObjectFieldMap,\n isPolicyAddressableField,\n isSensitiveField,\n isTransientField,\n} from './field-definitions.js';\nimport type { FieldPolicyAuditSnapshot } from './types.js';\n\nconst DEFAULT_PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 100;\nconst FORM_TYPES = new Set([\n 'text',\n 'integer',\n 'decimal',\n 'boolean',\n 'datetime',\n 'json',\n 'foreignKey',\n 'crossPackageRef',\n]);\nconst SYSTEM_NAMES = new Set([\n 'id',\n 'slug',\n 'context',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n 'deletedAt',\n 'deleted_at',\n 'tenantId',\n 'tenant_id',\n]);\n\nexport interface FieldPolicyCatalogField {\n type:\n | 'text'\n | 'integer'\n | 'decimal'\n | 'boolean'\n | 'datetime'\n | 'json'\n | 'foreignKey'\n | 'crossPackageRef';\n required?: boolean;\n default?: unknown;\n description?: string;\n ui?: { basic?: boolean; group?: string; order?: number; locked?: boolean };\n}\n\nexport interface FieldPolicySummaryItem {\n id: string;\n label: string;\n description?: string;\n eyebrow?: string;\n status?: string;\n objectRef: string;\n fieldName: string;\n className: string;\n packageName: string;\n}\n\nexport interface FieldPolicyDetailItem extends FieldPolicySummaryItem {\n fields: Record<string, FieldPolicyCatalogField>;\n}\n\n/** Structural SettingsCatalogPage mirror; `SettingsCatalog` accepts it directly. */\nexport interface FieldPolicySettingsCatalogPage {\n items: FieldPolicySummaryItem[];\n selected: FieldPolicyDetailItem | null;\n query: string;\n page: number;\n pageSize: number;\n total: number;\n}\n\nexport interface FieldPolicyCatalogObjectSummary {\n objectRef: string;\n className: string;\n packageName: string;\n fieldCount: number;\n}\n\nexport interface FieldPolicySettingsCatalogData {\n page: FieldPolicySettingsCatalogPage;\n audit: FieldPolicyAuditSnapshot;\n objects: FieldPolicyCatalogObjectSummary[];\n packages: string[];\n filters: {\n packageFilter: string | null;\n objectFilter: string | null;\n customizedOnly: boolean;\n };\n}\n\nexport interface FieldPolicySettingsCatalogQuery {\n query?: string | null;\n page?: number | null;\n pageSize?: number | null;\n selectedId?: string | null;\n packageFilter?: string | null;\n objectFilter?: string | null;\n customizedOnly?: boolean;\n}\n\nexport interface BuildFieldPolicySettingsCatalogOptions\n extends FieldPolicySettingsCatalogQuery {\n db?: SmrtClassOptions['db'];\n collection?: FieldPolicyCollection;\n objectRefs?: string[];\n}\n\ninterface CatalogObject {\n objectRef: string;\n className: string;\n packageName: string;\n fields: Record<string, FieldPolicyCatalogField>;\n}\n\nexport function fieldPolicyCatalogItemId(\n objectRef: string,\n fieldName: string,\n): string {\n return `${objectRef}::${fieldName}`;\n}\n\nexport function parseFieldPolicyCatalogQuery(\n params: URLSearchParams,\n): FieldPolicySettingsCatalogQuery {\n return {\n query: params.get('q'),\n page: integerParam(params.get('page')),\n pageSize: integerParam(params.get('pageSize')),\n selectedId: params.get('selected'),\n packageFilter: params.get('package'),\n objectFilter: params.get('object'),\n customizedOnly: params.get('customized') === '1',\n };\n}\n\nexport async function buildFieldPolicySettingsCatalog(\n options: BuildFieldPolicySettingsCatalogOptions,\n): Promise<FieldPolicySettingsCatalogData> {\n const collection =\n options.collection ??\n (options.db\n ? await FieldPolicyCollection.create({ db: options.db })\n : null);\n if (!collection) throw new Error('A db or FieldPolicyCollection is required');\n\n const filters = {\n packageFilter: stringFilter(options.packageFilter),\n objectFilter: stringFilter(options.objectFilter),\n customizedOnly: options.customizedOnly === true,\n };\n const query = options.query?.trim() ?? '';\n const pageSize = clamp(options.pageSize, 1, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);\n // Authorization is established before registry enumeration, but this must\n // stay a capability-only call: no policy rows are read until the URL-driven\n // page (or the explicit customized filter) identifies its object refs.\n const baseAudit = await collection.policyAudit({ summaryOnly: true });\n if (!baseAudit.caller.canManageOrg) {\n return {\n page: emptyPage(query, pageSize),\n audit: baseAudit,\n objects: [],\n packages: [],\n filters,\n };\n }\n\n const objects = await listCatalogObjects(options.objectRefs);\n const candidates = objects.filter(\n (object) =>\n (!filters.packageFilter ||\n object.packageName === filters.packageFilter) &&\n (!filters.objectFilter || object.objectRef === filters.objectFilter),\n );\n const allCandidateRefs = candidates.map((object) => object.objectRef);\n const countAudit =\n filters.customizedOnly && allCandidateRefs.length\n ? await loadAuditCounts(collection, allCandidateRefs, baseAudit)\n : baseAudit;\n const customized = customizedKeys(countAudit);\n const entries = candidates.flatMap((object) =>\n Object.entries(object.fields)\n .filter(\n ([fieldName]) =>\n !filters.customizedOnly ||\n customized.has(fieldPolicyCatalogItemId(object.objectRef, fieldName)),\n )\n .map(([fieldName, field]) => ({\n item: {\n id: fieldPolicyCatalogItemId(object.objectRef, fieldName),\n label: fieldName,\n ...(field.description ? { description: field.description } : {}),\n eyebrow: `${object.className} · ${object.packageName}`,\n objectRef: object.objectRef,\n fieldName,\n className: object.className,\n packageName: object.packageName,\n },\n search:\n `${fieldName} ${spaced(fieldName)} ${object.className} ${object.packageName} ${object.objectRef} ${field.description ?? ''}`.toLowerCase(),\n })),\n );\n const filtered = query\n ? entries.filter((entry) => entry.search.includes(query.toLowerCase()))\n : entries;\n const total = filtered.length;\n const page = clamp(\n options.page,\n 1,\n Math.max(1, Math.ceil(total / pageSize)),\n 1,\n );\n const items = filtered\n .slice((page - 1) * pageSize, page * pageSize)\n .map((entry) => entry.item);\n const selectedSummary =\n (options.selectedId\n ? filtered.find((entry) => entry.item.id === options.selectedId)?.item\n : undefined) ?? items[0];\n const selected = selectedSummary\n ? {\n ...selectedSummary,\n fields:\n objects.find(\n (object) => object.objectRef === selectedSummary.objectRef,\n )?.fields ?? {},\n }\n : null;\n const auditRefs = selected\n ? uniqueRefs([selected.objectRef, ...items.map((item) => item.objectRef)])\n : [];\n const audit = auditRefs.length\n ? await collection.policyAudit({\n objectRefs: auditRefs.slice(0, MAX_FIELD_POLICY_AUDIT_OBJECT_REFS),\n countObjectRefs: auditRefs,\n includeDrift: true,\n })\n : await collection.policyAudit({ includeDrift: true });\n return {\n page: { items, selected, query, page, pageSize, total },\n audit,\n objects: objects.map(({ objectRef, className, packageName, fields }) => ({\n objectRef,\n className,\n packageName,\n fieldCount: Object.keys(fields).length,\n })),\n packages: [...new Set(objects.map((object) => object.packageName))].sort(),\n filters,\n };\n}\n\nasync function listCatalogObjects(\n objectRefs?: string[],\n): Promise<CatalogObject[]> {\n const refs: string[] = objectRefs\n ? [...objectRefs]\n : Array.from(ObjectRegistry.getPublicClasses().values()).reduce<string[]>(\n (result, registered) => {\n if (registered.qualifiedName) result.push(registered.qualifiedName);\n return result;\n },\n [],\n );\n const unique = [...new Set(refs)].sort();\n const objects: CatalogObject[] = [];\n for (const objectRef of unique) {\n const registered = ObjectRegistry.getClassByQualifiedName(objectRef);\n if (\n !registered ||\n ObjectRegistry.getTableName(objectRef)?.startsWith('_smrt_')\n )\n continue;\n const fields = await getObjectFieldMap(objectRef);\n const selected: Record<string, FieldPolicyCatalogField> = {};\n for (const [name, definition] of fields) {\n if (\n SYSTEM_NAMES.has(name) ||\n !isPolicyAddressableField(definition) ||\n isSensitiveField(definition) ||\n isTransientField(definition) ||\n getFieldReadPermission(definition) !== undefined ||\n !FORM_TYPES.has(String(definition.type))\n )\n continue;\n selected[name] = {\n type: definition.type as FieldPolicyCatalogField['type'],\n ...(definition.required === true ? { required: true } : {}),\n ...(definition.default !== undefined\n ? { default: definition.default }\n : {}),\n ...(typeof definition.description === 'string'\n ? { description: definition.description }\n : {}),\n };\n }\n if (!Object.keys(selected).length) continue;\n const colon = objectRef.lastIndexOf(':');\n const packageName = colon === -1 ? '' : objectRef.slice(0, colon);\n const className = colon === -1 ? objectRef : objectRef.slice(colon + 1);\n objects.push({ objectRef, className, packageName, fields: selected });\n }\n return objects;\n}\n\nasync function loadAuditCounts(\n collection: FieldPolicyCollection,\n refs: string[],\n baseline: FieldPolicyAuditSnapshot,\n): Promise<FieldPolicyAuditSnapshot> {\n const userOverrideCounts: FieldPolicyAuditSnapshot['userOverrideCounts'] = {};\n const orgRows: FieldPolicyAuditSnapshot['orgRows'] = [];\n const appRows: FieldPolicyAuditSnapshot['appRows'] = [];\n const inheritedOrgKeys: FieldPolicyAuditSnapshot['inheritedOrgKeys'] = {};\n const chunkSize = MAX_FIELD_POLICY_AUDIT_OBJECT_REFS;\n for (let offset = 0; offset < refs.length; offset += chunkSize) {\n const audit = await collection.policyAudit({\n objectRefs: refs.slice(offset, offset + chunkSize),\n countObjectRefs: refs.slice(offset, offset + chunkSize),\n countsOnly: true,\n });\n for (const [objectRef, byField] of Object.entries(\n audit.userOverrideCounts,\n )) {\n userOverrideCounts[objectRef] = {\n ...(userOverrideCounts[objectRef] ?? {}),\n ...byField,\n };\n }\n orgRows.push(...audit.orgRows);\n appRows.push(...audit.appRows);\n for (const [objectRef, fieldNames] of Object.entries(\n audit.inheritedOrgKeys,\n )) {\n const names = inheritedOrgKeys[objectRef] ?? [];\n inheritedOrgKeys[objectRef] = names;\n for (const fieldName of fieldNames) {\n if (!names.includes(fieldName)) names.push(fieldName);\n }\n }\n }\n return {\n ...baseline,\n orgRows,\n appRows,\n inheritedOrgKeys,\n userOverrideCounts,\n };\n}\n\nfunction customizedKeys(audit: FieldPolicyAuditSnapshot): Set<string> {\n const keys = new Set<string>();\n for (const row of [...audit.orgRows, ...audit.appRows])\n keys.add(fieldPolicyCatalogItemId(row.objectRef, row.fieldName));\n for (const [objectRef, names] of Object.entries(audit.inheritedOrgKeys))\n for (const name of names)\n keys.add(fieldPolicyCatalogItemId(objectRef, name));\n for (const [objectRef, fields] of Object.entries(audit.userOverrideCounts))\n for (const [name, count] of Object.entries(fields))\n if (count > 0) keys.add(fieldPolicyCatalogItemId(objectRef, name));\n return keys;\n}\n\nfunction emptyPage(\n query: string,\n pageSize: number,\n): FieldPolicySettingsCatalogPage {\n return { items: [], selected: null, query, page: 1, pageSize, total: 0 };\n}\nfunction uniqueRefs(refs: string[]): string[] {\n return [...new Set(refs)];\n}\nfunction stringFilter(value: string | null | undefined): string | null {\n const trimmed = value?.trim();\n return trimmed ? trimmed : null;\n}\nfunction integerParam(value: string | null): number | null {\n return value && /^\\d+$/.test(value) ? Number(value) : null;\n}\nfunction clamp(\n value: number | null | undefined,\n min: number,\n max: number,\n fallback: number,\n): number {\n return Number.isFinite(value)\n ? Math.min(max, Math.max(min, Math.trunc(value as number)))\n : fallback;\n}\nfunction spaced(value: string): string {\n return value.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]/g, ' ');\n}\n","/**\n * The #2051 learning loop: scheduled aggregation/retention over\n * `_smrt_field_usage_counters` and threshold-driven generation of\n * `_smrt_field_policy_suggestions`.\n *\n * Scheduling substrate: there is NO generic cron package API — the de-facto\n * convention is an `AgentSchedule` row (`@happyvertical/smrt-agents`)\n * dispatched by smrt-jobs' `ScheduleRunner`, which resolves `agentType`\n * through the `ObjectRegistry` and invokes the method on the registered\n * class. {@link FieldUsageLearningAgent} is that schedule target. It is a\n * registered `@smrt()` SmrtObject — deliberately NOT a subclass of the agents\n * package's `Agent` base: smrt-agents sits above smrt-fields in the\n * dependency DAG (it hard-depends on smrt-users/ai/secrets, all of which this\n * package keeps optional), and the dispatch machinery only requires registry\n * resolution plus the jobs package's `backgroundEligibleMethods` allowlist\n * contract (a static property, no import needed). Schedule rows are created\n * DORMANT by `ensureFieldUsageLearningSchedules` (see `usage-schedules.ts`).\n *\n * Both jobs are tenant-safe by construction: in trusted execution (no ambient\n * tenant context, or super-admin bypass) they operate across all tenants;\n * inside a non-bypass tenant context they restrict themselves to the ambient\n * tenant (fail closed), so per-tenant schedule rows are also valid.\n */\n\nimport {\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { FieldPolicySuggestionCollection } from './collections/FieldPolicySuggestionCollection.js';\nimport {\n decodeHistogramKey,\n FieldUsageCounterCollection,\n isHistogramEligibleField,\n} from './collections/FieldUsageCounterCollection.js';\nimport {\n type FieldDefinitionMap,\n getFieldReadPermission,\n getObjectFieldMap,\n isSensitiveField,\n isTransientField,\n} from './field-definitions.js';\nimport {\n ACTIVE_SUGGESTION_KEY,\n type FieldPolicySuggestion,\n} from './models/FieldPolicySuggestion.js';\nimport type { FieldUsageCounter } from './models/FieldUsageCounter.js';\nimport {\n emptyHistogram,\n fieldUsagePeriodForDate,\n} from './models/FieldUsageCounter.js';\nimport type {\n FieldPolicySuggestionKind,\n ResolvedFieldPolicy,\n} from './types.js';\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\n// ---------------------------------------------------------------------------\n// Config (documented defaults; overridable per run through the schedule's\n// `methodArgs` payload)\n// ---------------------------------------------------------------------------\n\nexport interface FieldUsageMaintenanceConfig {\n /** Drop counter buckets older than this (by `period`). Default 90 days. */\n counterMaxAgeMs: number;\n /** Keep at most this many counter rows (oldest pruned first). Default 100k. */\n counterMaxRows: number;\n /** Drop ACCEPTED suggestions decided longer ago than this. Default 180 days. */\n suggestionAcceptedMaxAgeMs: number;\n}\n\nexport const FIELD_USAGE_MAINTENANCE_DEFAULTS: FieldUsageMaintenanceConfig = {\n counterMaxAgeMs: 90 * DAY_MS,\n counterMaxRows: 100_000,\n suggestionAcceptedMaxAgeMs: 180 * DAY_MS,\n};\n\nexport interface FieldUsageSuggestionConfig {\n /** Usage window the thresholds evaluate over. Default 30 days. */\n windowDays: number;\n /** Distinct users required for a `promote` suggestion. Default 5. */\n minDistinctUsers: number;\n /** Submissions required before a `default` suggestion. Default 10. */\n minSetCount: number;\n /**\n * Share of windowed submissions a single value must reach for a `default`\n * suggestion (denominator is TOTAL setCount, so histogram overflow can only\n * make this more conservative). Default 0.8.\n */\n defaultDominanceRatio: number;\n}\n\nexport const FIELD_USAGE_SUGGESTION_DEFAULTS: FieldUsageSuggestionConfig = {\n windowDays: 30,\n minDistinctUsers: 5,\n minSetCount: 10,\n defaultDominanceRatio: 0.8,\n};\n\nexport interface FieldUsageMaintenanceSummary {\n countersPruned: number;\n receiptsPruned: number;\n suggestionsPruned: number;\n}\n\nexport interface FieldUsageSuggestionRunSummary {\n /**\n * Suggestions this run wrote. Under overlapping runs both may report a\n * create for the same candidate while the model's active-slot unique index\n * converges them onto ONE row (see `FieldPolicySuggestion.activeKey`) — the\n * tally is per-run work, not a row count.\n */\n created: number;\n /** Candidates suppressed by a pending or cooling-down suggestion. */\n suppressed: number;\n /** Distinct (tenant, objectRef, fieldName) groups evaluated. */\n groupsConsidered: number;\n /**\n * Groups whose evaluation threw and was skipped. A failing group never\n * aborts the run — a global pass must keep serving every other tenant.\n */\n groupsFailed: number;\n /**\n * Bounded sample of failure messages (`MAX_REPORTED_GROUP_FAILURES`) so an\n * operator can see WHY without the summary growing with the queue.\n */\n failures: string[];\n}\n\n/** Cap on {@link FieldUsageSuggestionRunSummary.failures} entries. */\nexport const MAX_REPORTED_GROUP_FAILURES = 5;\n\n// ---------------------------------------------------------------------------\n// Retention (the pruneChangeFeed shape: {maxAgeMs?, maxRows?}, oldest-first)\n// ---------------------------------------------------------------------------\n\nexport interface FieldUsageCounterRetention {\n maxAgeMs?: number;\n maxRows?: number;\n /** Restrict pruning to one tenant (ambient-context runs). */\n tenantId?: string | null;\n}\n\n/**\n * Prune counter rows to bound growth. Applies whichever bounds are provided\n * (at least one required): `maxAgeMs` drops buckets whose `period` day is\n * older than the cutoff; `maxRows` keeps only the newest N rows by\n * `(period, id)`, deleting oldest-first. Mirrors core's `pruneChangeFeed`.\n */\nexport async function pruneFieldUsageCounters(\n db: DatabaseInterface,\n retention: FieldUsageCounterRetention,\n): Promise<{ pruned: number }> {\n const { maxAgeMs, maxRows } = retention;\n if (maxAgeMs == null && maxRows == null) {\n throw new Error('pruneFieldUsageCounters requires maxAgeMs and/or maxRows');\n }\n if (maxAgeMs != null && (!Number.isFinite(maxAgeMs) || maxAgeMs < 0)) {\n throw new Error(\n `pruneFieldUsageCounters maxAgeMs must be >= 0, got ${maxAgeMs}`,\n );\n }\n if (maxRows != null && (!Number.isFinite(maxRows) || maxRows < 0)) {\n throw new Error(\n `pruneFieldUsageCounters maxRows must be >= 0, got ${maxRows}`,\n );\n }\n\n const tenantCondition = retention.tenantId ? ' AND tenant_id = ?' : '';\n const tenantParams = retention.tenantId ? [retention.tenantId] : [];\n let pruned = 0;\n\n if (maxAgeMs != null) {\n const cutoffDay = fieldUsagePeriodForDate(new Date(Date.now() - maxAgeMs));\n pruned += await deleteCounted(db, `period < ?${tenantCondition}`, [\n cutoffDay,\n ...tenantParams,\n ]);\n }\n\n if (maxRows != null) {\n const countRows = getQueryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM _smrt_field_usage_counters` +\n `${retention.tenantId ? ' WHERE tenant_id = ?' : ''}`,\n ...tenantParams,\n ),\n );\n const total = numberFromRow(countRows[0] ?? {}, 'total');\n const excess = total - Math.floor(maxRows);\n if (excess > 0) {\n await db.query(\n `DELETE FROM _smrt_field_usage_counters\n WHERE id IN (\n SELECT id FROM _smrt_field_usage_counters\n ${retention.tenantId ? 'WHERE tenant_id = ?' : ''}\n ORDER BY period ASC, id ASC\n LIMIT ${excess}\n )`,\n ...tenantParams,\n );\n pruned += excess;\n }\n }\n\n return { pruned };\n}\n\n/**\n * Drop durable anti-inflation receipts at the same age cutoff as counters.\n *\n * Deliberately do NOT delete a receipt merely because its counter row is\n * absent: report ingestion claims its receipt before merging the counter, so\n * an orphan sweep could otherwise reopen the once-per-user/day quota during\n * that in-flight interval. A max-row counter trim likewise leaves its recent\n * receipts until the age cutoff — retaining a de-duplication guard is safer\n * than allowing a second contribution for that day.\n */\nexport async function pruneFieldUsageReportReceipts(\n db: DatabaseInterface,\n options: { maxAgeMs: number; tenantId?: string | null },\n): Promise<{ pruned: number }> {\n if (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0) {\n throw new Error(\n `pruneFieldUsageReportReceipts maxAgeMs must be >= 0, got ` +\n `${options.maxAgeMs}`,\n );\n }\n const cutoffDay = fieldUsagePeriodForDate(\n new Date(Date.now() - options.maxAgeMs),\n );\n const tenantCondition = options.tenantId ? ' AND tenant_id = ?' : '';\n const tenantParams = options.tenantId ? [options.tenantId] : [];\n return {\n pruned: await deleteCounted(\n db,\n `period < ?${tenantCondition}`,\n [cutoffDay, ...tenantParams],\n '_smrt_field_usage_report_receipts',\n ),\n };\n}\n\nexport interface FieldPolicySuggestionRetention {\n /** Drop ACCEPTED suggestions decided longer ago than this. */\n acceptedMaxAgeMs: number;\n /** Restrict pruning to one tenant (ambient-context runs). */\n tenantId?: string | null;\n /** Clock override for deterministic tests. */\n now?: Date;\n}\n\n/**\n * Prune settled suggestion rows ONLY (#2051 pin): a dismissed suggestion once\n * its cool-down has fully elapsed (its suppression job is done), and an\n * accepted suggestion once it is old. Pending suggestions are NEVER pruned.\n */\nexport async function pruneFieldPolicySuggestions(\n db: DatabaseInterface,\n retention: FieldPolicySuggestionRetention,\n): Promise<{ pruned: number }> {\n const { acceptedMaxAgeMs } = retention;\n if (!Number.isFinite(acceptedMaxAgeMs) || acceptedMaxAgeMs < 0) {\n throw new Error(\n `pruneFieldPolicySuggestions acceptedMaxAgeMs must be >= 0, got ` +\n `${acceptedMaxAgeMs}`,\n );\n }\n const now = retention.now ?? new Date();\n const acceptedCutoff = new Date(now.getTime() - acceptedMaxAgeMs);\n const tenantCondition = retention.tenantId ? ' AND tenant_id = ?' : '';\n const tenantParams = retention.tenantId ? [retention.tenantId] : [];\n\n const pruned = await deleteCounted(\n db,\n `((status = 'dismissed' AND cooldown_until IS NOT NULL ` +\n `AND cooldown_until <= ?) ` +\n `OR (status = 'accepted' AND decided_at IS NOT NULL ` +\n `AND decided_at <= ?))${tenantCondition}`,\n [now.toISOString(), acceptedCutoff.toISOString(), ...tenantParams],\n '_smrt_field_policy_suggestions',\n );\n return { pruned };\n}\n\n// ---------------------------------------------------------------------------\n// Job entry points (pure functions; the agent methods delegate here)\n// ---------------------------------------------------------------------------\n\nexport interface RunFieldUsageMaintenanceOptions\n extends Partial<FieldUsageMaintenanceConfig> {\n db: DatabaseInterface;\n}\n\n/**\n * The \"aggregation\" schedule's work. Ingestion pre-aggregates into period\n * buckets, so the roll-up job's real job is retention: prune counter buckets\n * and durable receipts at the shared age cutoff, then settle old suggestion\n * rows. Max-row trimming leaves recent receipts intact to preserve daily\n * de-duplication while an ingestion merge is in flight.\n */\nexport async function runFieldUsageMaintenance(\n options: RunFieldUsageMaintenanceOptions,\n): Promise<FieldUsageMaintenanceSummary> {\n const config = normalizeMaintenanceConfig(options);\n const ambientTenantId = restrictingTenantId();\n\n const counters = await pruneFieldUsageCounters(options.db, {\n maxAgeMs: config.counterMaxAgeMs,\n maxRows: config.counterMaxRows,\n tenantId: ambientTenantId,\n });\n const receipts = await pruneFieldUsageReportReceipts(options.db, {\n maxAgeMs: config.counterMaxAgeMs,\n tenantId: ambientTenantId,\n });\n const suggestions = await pruneFieldPolicySuggestions(options.db, {\n acceptedMaxAgeMs: config.suggestionAcceptedMaxAgeMs,\n tenantId: ambientTenantId,\n });\n\n return {\n countersPruned: counters.pruned,\n receiptsPruned: receipts.pruned,\n suggestionsPruned: suggestions.pruned,\n };\n}\n\nexport interface RunFieldPolicySuggestionGenerationOptions\n extends Partial<FieldUsageSuggestionConfig> {\n db: DatabaseInterface;\n /** Clock override for deterministic tests. */\n now?: Date;\n}\n\n/**\n * The threshold job: evaluate windowed counters per\n * `(tenantId, objectRef, fieldName)` and create PENDING suggestions with\n * human-readable evidence.\n *\n * - `promote`: at least `minDistinctUsers` distinct users set the field to a\n * non-default value in the window AND the org-resolved visibility is not\n * already `basic`.\n * - `default`: at least `minSetCount` TOTAL submissions, a single recorded\n * value covers `defaultDominanceRatio` of those TOTAL submissions (not of\n * the deviations — see `FieldUsageCounter.submissionCount`), and it differs\n * from the org-resolved default. Only histogram-eligible fields\n * (low-cardinality, non-sensitive, non-gated) can ever qualify, and a group\n * containing a legacy bucket with no recorded total is skipped rather than\n * ratioed against the wrong denominator.\n *\n * Dedup: a candidate is suppressed while the same\n * `(tenantId, objectRef, fieldName, kind)` has a PENDING suggestion or a\n * DISMISSED one still inside its cool-down. Accepted history never blocks —\n * once accepted, the resolved policy itself stops regeneration (visibility is\n * basic / the default matches). The pre-check is an optimization only: the\n * single-active guarantee is STRUCTURAL (`FieldPolicySuggestion.activeKey` in\n * `conflictColumns`), so overlapping runs upsert onto one row instead of\n * duplicating.\n *\n * Sensitive, read-permission-gated, and transient fields are skipped\n * entirely: their usage rows are count-only observability data and never\n * produce suggestions.\n */\nexport async function runFieldPolicySuggestionGeneration(\n options: RunFieldPolicySuggestionGenerationOptions,\n): Promise<FieldUsageSuggestionRunSummary> {\n const config = normalizeSuggestionConfig(options);\n const now = options.now ?? new Date();\n const ambientTenantId = restrictingTenantId();\n\n const counters = await FieldUsageCounterCollection.create({\n db: options.db,\n });\n const suggestions = await FieldPolicySuggestionCollection.create({\n db: options.db,\n });\n\n const fromPeriod = fieldUsagePeriodForDate(\n new Date(now.getTime() - (config.windowDays - 1) * DAY_MS),\n );\n const toPeriod = fieldUsagePeriodForDate(now);\n const rows = await counters.listWindow({\n fromPeriod,\n toPeriod,\n tenantId: ambientTenantId,\n });\n\n const groups = groupCounters(rows);\n const fieldMaps = new Map<string, FieldDefinitionMap | null>();\n const resolvedPolicies = new Map<\n string,\n Record<string, ResolvedFieldPolicy>\n >();\n\n const summary: FieldUsageSuggestionRunSummary = {\n created: 0,\n suppressed: 0,\n groupsConsidered: 0,\n groupsFailed: 0,\n failures: [],\n };\n\n for (const group of groups) {\n summary.groupsConsidered += 1;\n\n // One bad group must NEVER abort the run: a global (cross-tenant) pass\n // processes every tenant, so an unprocessable group — a since-changed\n // field, a rejected proposal payload, a transient db error — would\n // otherwise starve every group after it for as long as its buckets live.\n // Failures are counted (with a bounded sample of messages) and the run\n // continues.\n try {\n await evaluateGroup(group);\n } catch (error) {\n summary.groupsFailed += 1;\n if (summary.failures.length < MAX_REPORTED_GROUP_FAILURES) {\n summary.failures.push(\n `${group.tenantId} ${group.objectRef}.${group.fieldName}: ` +\n `${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n\n return summary;\n\n async function evaluateGroup(group: CounterGroup): Promise<void> {\n let fieldMap = fieldMaps.get(group.objectRef);\n if (fieldMap === undefined) {\n try {\n fieldMap = await getObjectFieldMap(group.objectRef);\n } catch {\n fieldMap = null; // stale counters for a since-removed class\n }\n fieldMaps.set(group.objectRef, fieldMap);\n }\n if (!fieldMap) {\n return;\n }\n const fieldDef = fieldMap.get(group.fieldName);\n if (\n !fieldDef ||\n isSensitiveField(fieldDef) ||\n getFieldReadPermission(fieldDef) !== undefined ||\n isTransientField(fieldDef)\n ) {\n return;\n }\n\n const stats = mergeGroupStats(group.buckets);\n\n const policyKey = `${group.tenantId}\\0${group.objectRef}`;\n let orgFields = resolvedPolicies.get(policyKey);\n if (!orgFields) {\n // Org-tier resolution (code → app → tenant chain; no user tier). The\n // resolver's own context assertion keeps ambient-context runs honest.\n const { resolveFieldPolicy } = await import('./field-policy-resolver.js');\n const resolved = await resolveFieldPolicy(group.objectRef, {\n tenantId: group.tenantId,\n db: options.db,\n });\n orgFields = resolved.fields;\n resolvedPolicies.set(policyKey, orgFields);\n }\n const fieldPolicy = orgFields[group.fieldName];\n if (!fieldPolicy) {\n return;\n }\n\n // -- promote ---------------------------------------------------------\n if (\n stats.distinctUsers >= config.minDistinctUsers &&\n fieldPolicy.visibility !== 'basic'\n ) {\n const created = await createUnlessSuppressed(suggestions, {\n tenantId: group.tenantId,\n objectRef: group.objectRef,\n fieldName: group.fieldName,\n kind: 'promote',\n proposedValue: null,\n evidence: buildFieldUsageEvidence({\n kind: 'promote',\n fieldName: group.fieldName,\n objectRef: group.objectRef,\n windowStart: fromPeriod,\n windowEnd: toPeriod,\n distinctUsers: stats.distinctUsers,\n distinctUsersAtLeast: stats.distinctUsersOverflowed,\n setCount: stats.setCount,\n submissionCount: stats.submissionTotalKnown\n ? stats.submissionCount\n : undefined,\n threshold: config.minDistinctUsers,\n }),\n now,\n });\n if (created) {\n summary.created += 1;\n } else {\n summary.suppressed += 1;\n }\n }\n\n // -- default ---------------------------------------------------------\n // Dominance is measured against TOTAL submissions, never against\n // deviations alone: a value seen only in deviations would otherwise look\n // 100% dominant even when the default it would replace is what almost\n // everyone submits. Legacy buckets (no recorded total) make the\n // denominator unknown, so the group is skipped rather than guessed.\n if (\n isHistogramEligibleField(fieldDef) &&\n stats.submissionTotalKnown &&\n stats.submissionCount >= config.minSetCount\n ) {\n const top = topHistogramEntry(stats.histogram);\n if (top) {\n const share = top.count / stats.submissionCount;\n const proposed = decodeHistogramKey(fieldDef, top.key);\n const currentDefault = fieldPolicy.hasDefault\n ? fieldPolicy.defaultValue\n : undefined;\n if (\n share >= config.defaultDominanceRatio &&\n !sameProposedValue(proposed, currentDefault)\n ) {\n const created = await createUnlessSuppressed(suggestions, {\n tenantId: group.tenantId,\n objectRef: group.objectRef,\n fieldName: group.fieldName,\n kind: 'default',\n proposedValue: JSON.stringify(proposed),\n evidence: buildFieldUsageEvidence({\n kind: 'default',\n fieldName: group.fieldName,\n objectRef: group.objectRef,\n windowStart: fromPeriod,\n windowEnd: toPeriod,\n distinctUsers: stats.distinctUsers,\n distinctUsersAtLeast: stats.distinctUsersOverflowed,\n setCount: stats.setCount,\n submissionCount: stats.submissionCount,\n threshold: config.minSetCount,\n topValue: proposed,\n topValueShare: share,\n }),\n now,\n });\n if (created) {\n summary.created += 1;\n } else {\n summary.suppressed += 1;\n }\n }\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// The schedule target\n// ---------------------------------------------------------------------------\n\n/**\n * The registered schedule target for the #2051 learning loop (see the module\n * doc for why it is NOT an smrt-agents `Agent` subclass). Rows of its system\n * table are never written — the class exists so `AgentSchedule.agentType`\n * resolves through the `ObjectRegistry` and smrt-jobs can construct it and\n * invoke the two allowlisted methods.\n */\n@smrt({\n tableName: '_smrt_field_usage_learning_agents',\n api: { include: [] },\n cli: false,\n mcp: { include: [] },\n})\nexport class FieldUsageLearningAgent extends SmrtObject {\n /**\n * The smrt-jobs opt-in background allowlist (S5 contract): ONLY these two\n * methods are reachable from a persisted job/schedule row.\n */\n static backgroundEligibleMethods: ReadonlyArray<string> = [\n 'runUsageMaintenance',\n 'runSuggestionGeneration',\n ];\n\n constructor(options: SmrtObjectOptions = {}) {\n super(options);\n }\n\n /** Schedule entry point for {@link runFieldUsageMaintenance}. */\n async runUsageMaintenance(\n args: Record<string, unknown> = {},\n ): Promise<FieldUsageMaintenanceSummary> {\n return runFieldUsageMaintenance({\n db: this.db,\n ...pickFiniteNumbers(args, [\n 'counterMaxAgeMs',\n 'counterMaxRows',\n 'suggestionAcceptedMaxAgeMs',\n ]),\n });\n }\n\n /** Schedule entry point for {@link runFieldPolicySuggestionGeneration}. */\n async runSuggestionGeneration(\n args: Record<string, unknown> = {},\n ): Promise<FieldUsageSuggestionRunSummary> {\n return runFieldPolicySuggestionGeneration({\n db: this.db,\n ...pickFiniteNumbers(args, [\n 'windowDays',\n 'minDistinctUsers',\n 'minSetCount',\n 'defaultDominanceRatio',\n ]),\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Evidence\n// ---------------------------------------------------------------------------\n\nexport interface FieldUsageEvidenceInput {\n kind: FieldPolicySuggestionKind;\n objectRef: string;\n fieldName: string;\n windowStart: string;\n windowEnd: string;\n distinctUsers: number;\n /** True when the distinct-user set overflowed (count is a lower bound). */\n distinctUsersAtLeast: boolean;\n /** Submissions that DIFFERED from the resolved default. */\n setCount: number;\n /**\n * TOTAL submissions observed in the window (the dominance denominator).\n * `undefined` only for legacy buckets that never recorded a total, in which\n * case the summary states the deviation count alone.\n */\n submissionCount?: number;\n /** The threshold the candidate cleared (documented in the evidence). */\n threshold: number;\n topValue?: unknown;\n topValueShare?: number;\n}\n\n/**\n * Human-readable evidence for a suggestion: a `summary` sentence a reviewer\n * can read as-is, plus the structured numbers behind it.\n *\n * The sentence always states BOTH numbers when known — deviations and total\n * submissions — so a reviewer can see the base rate a dominance percentage was\n * computed against instead of trusting a bare ratio.\n */\nexport function buildFieldUsageEvidence(\n input: FieldUsageEvidenceInput,\n): Record<string, unknown> {\n const users = `${input.distinctUsersAtLeast ? 'at least ' : ''}${\n input.distinctUsers\n } user${input.distinctUsers === 1 && !input.distinctUsersAtLeast ? '' : 's'}`;\n const deviations =\n `${input.setCount} of ` +\n (input.submissionCount === undefined\n ? 'an unrecorded number of submissions'\n : `${input.submissionCount} submission${\n input.submissionCount === 1 ? '' : 's'\n }`);\n const base =\n `${users} set \"${input.fieldName}\" to a non-default value in ` +\n `${deviations} between ${input.windowStart} and ${input.windowEnd}.`;\n const summary =\n input.kind === 'default'\n ? `${base} ${Math.round((input.topValueShare ?? 0) * 100)}% of all ` +\n `${input.submissionCount ?? 0} submissions used the value ` +\n `${JSON.stringify(input.topValue)}.`\n : base;\n\n return {\n summary,\n objectRef: input.objectRef,\n fieldName: input.fieldName,\n windowStart: input.windowStart,\n windowEnd: input.windowEnd,\n distinctUsers: input.distinctUsers,\n ...(input.distinctUsersAtLeast ? { distinctUsersAtLeast: true } : {}),\n setCount: input.setCount,\n ...(input.submissionCount !== undefined\n ? { submissionCount: input.submissionCount }\n : { submissionCountUnknown: true }),\n threshold: input.threshold,\n ...(input.topValue !== undefined ? { topValue: input.topValue } : {}),\n ...(input.topValueShare !== undefined\n ? { topValueShare: Number(input.topValueShare.toFixed(4)) }\n : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface CounterGroup {\n tenantId: string;\n objectRef: string;\n fieldName: string;\n buckets: FieldUsageCounter[];\n}\n\nfunction groupCounters(rows: FieldUsageCounter[]): CounterGroup[] {\n const byKey = new Map<string, CounterGroup>();\n for (const row of rows) {\n if (!row.tenantId) {\n continue;\n }\n const key = `${row.tenantId}\\0${row.objectRef}\\0${row.fieldName}`;\n let group = byKey.get(key);\n if (!group) {\n group = {\n tenantId: row.tenantId,\n objectRef: row.objectRef,\n fieldName: row.fieldName,\n buckets: [],\n };\n byKey.set(key, group);\n }\n group.buckets.push(row);\n }\n return [...byKey.values()];\n}\n\ninterface GroupStats {\n /** Total submissions in the window (the dominance denominator). */\n submissionCount: number;\n /**\n * False when ANY bucket in the window predates the `submissionCount` column\n * (or is corrupt): the total is then unknown, so `default` suggestions are\n * skipped for the group rather than computed against a wrong denominator.\n */\n submissionTotalKnown: boolean;\n /** Submissions that differed from the resolved default. */\n setCount: number;\n distinctUsers: number;\n distinctUsersOverflowed: boolean;\n histogram: Record<string, number>;\n}\n\n/**\n * Merge a group's buckets: sum counts, UNION the capped distinct-user sets\n * (an overflowed bucket makes the union an honest lower bound), and sum\n * histogram buckets.\n */\nfunction mergeGroupStats(buckets: FieldUsageCounter[]): GroupStats {\n let submissionCount = 0;\n let submissionTotalKnown = true;\n let setCount = 0;\n let overflowed = false;\n const users = new Set<string>();\n // Null-prototype: histogram keys are user-supplied ids, so `constructor` /\n // `toString` / `__proto__` must accumulate as plain data (see\n // `emptyHistogram`). The `hasOwn` read below is the matching own-key test.\n const histogram = emptyHistogram();\n\n for (const bucket of buckets) {\n submissionCount += bucket.submissionCount;\n if (bucket.isLegacyBucket()) {\n submissionTotalKnown = false;\n }\n setCount += bucket.setCount;\n overflowed = overflowed || bucket.distinctUsersOverflowed;\n for (const id of bucket.getDistinctUserIds()) {\n users.add(id);\n }\n for (const [key, count] of Object.entries(bucket.getValueHistogram())) {\n histogram[key] =\n (Object.hasOwn(histogram, key) ? histogram[key] : 0) + count;\n }\n }\n\n return {\n submissionCount,\n submissionTotalKnown,\n setCount,\n distinctUsers: users.size,\n distinctUsersOverflowed: overflowed,\n histogram,\n };\n}\n\nfunction topHistogramEntry(\n histogram: Record<string, number>,\n): { key: string; count: number } | null {\n let top: { key: string; count: number } | null = null;\n for (const [key, count] of Object.entries(histogram)) {\n if (!top || count > top.count) {\n top = { key, count };\n }\n }\n return top;\n}\n\nfunction sameProposedValue(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true;\n }\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n}\n\nasync function createUnlessSuppressed(\n suggestions: FieldPolicySuggestionCollection,\n candidate: {\n tenantId: string;\n objectRef: string;\n fieldName: string;\n kind: FieldPolicySuggestionKind;\n proposedValue: string | null;\n evidence: Record<string, unknown>;\n now: Date;\n },\n): Promise<boolean> {\n const existing = await suggestions.list({\n where: {\n tenantId: candidate.tenantId,\n objectRef: candidate.objectRef,\n fieldName: candidate.fieldName,\n kind: candidate.kind,\n },\n });\n // Suppression keys off the ACTIVE SLOT, not `status`: a row holding\n // `activeKey === 'active'` is either pending or mid-decision (the\n // non-transactional accept holds the slot across its policy write). Keying\n // off `status` alone would let generation insert a competing pending row in\n // that window — which would then resolve against the pre-acceptance policy\n // and collide with the decision's compensation. Cooling-down dismissals\n // suppress too, even though they have released the slot.\n const suppressed = existing.some(\n (row) =>\n row.activeKey === ACTIVE_SUGGESTION_KEY ||\n (row.status === 'dismissed' && isWithinCoolDown(row, candidate.now)),\n );\n if (suppressed) {\n return false;\n }\n\n await suggestions.create({\n tenantId: candidate.tenantId,\n objectRef: candidate.objectRef,\n fieldName: candidate.fieldName,\n kind: candidate.kind,\n proposedValue: candidate.proposedValue,\n evidence: JSON.stringify(candidate.evidence),\n status: 'pending',\n });\n return true;\n}\n\nfunction isWithinCoolDown(row: FieldPolicySuggestion, now: Date): boolean {\n const raw = row.cooldownUntil as Date | string | null;\n if (raw === null || raw === undefined) {\n return false;\n }\n const until = raw instanceof Date ? raw.getTime() : Date.parse(String(raw));\n return Number.isFinite(until) && until > now.getTime();\n}\n\n/** Ambient-context restriction: non-bypass runs stay inside their tenant. */\nfunction restrictingTenantId(): string | null {\n const context = getCurrentTenant();\n if (!context || isSuperAdminBypass()) {\n return null;\n }\n return context.tenantId;\n}\n\n/**\n * Retention bounds are NON-NEGATIVE, not strictly positive: `0` is a meaningful\n * value the prune functions explicitly accept (`maxRows: 0` purges every row,\n * `maxAgeMs: 0` every bucket before today), so silently swapping it for the\n * 100k/90d default would ignore an operator's explicit \"purge everything\".\n * Only `undefined` (and non-finite junk) falls back to the default.\n */\nfunction normalizeMaintenanceConfig(\n options: Partial<FieldUsageMaintenanceConfig>,\n): FieldUsageMaintenanceConfig {\n return {\n counterMaxAgeMs: nonNegativeOrDefault(\n options.counterMaxAgeMs,\n FIELD_USAGE_MAINTENANCE_DEFAULTS.counterMaxAgeMs,\n ),\n counterMaxRows: nonNegativeOrDefault(\n options.counterMaxRows,\n FIELD_USAGE_MAINTENANCE_DEFAULTS.counterMaxRows,\n ),\n suggestionAcceptedMaxAgeMs: nonNegativeOrDefault(\n options.suggestionAcceptedMaxAgeMs,\n FIELD_USAGE_MAINTENANCE_DEFAULTS.suggestionAcceptedMaxAgeMs,\n ),\n };\n}\n\nfunction normalizeSuggestionConfig(\n options: Partial<FieldUsageSuggestionConfig>,\n): FieldUsageSuggestionConfig {\n const ratio = positiveOrDefault(\n options.defaultDominanceRatio,\n FIELD_USAGE_SUGGESTION_DEFAULTS.defaultDominanceRatio,\n );\n return {\n windowDays: Math.max(\n 1,\n Math.floor(\n positiveOrDefault(\n options.windowDays,\n FIELD_USAGE_SUGGESTION_DEFAULTS.windowDays,\n ),\n ),\n ),\n minDistinctUsers: Math.max(\n 1,\n Math.floor(\n positiveOrDefault(\n options.minDistinctUsers,\n FIELD_USAGE_SUGGESTION_DEFAULTS.minDistinctUsers,\n ),\n ),\n ),\n minSetCount: Math.max(\n 1,\n Math.floor(\n positiveOrDefault(\n options.minSetCount,\n FIELD_USAGE_SUGGESTION_DEFAULTS.minSetCount,\n ),\n ),\n ),\n defaultDominanceRatio: Math.min(Math.max(ratio, 0.01), 1),\n };\n}\n\n/** Strictly positive knobs (thresholds, windows): `0` is not meaningful. */\nfunction positiveOrDefault(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0\n ? value\n : fallback;\n}\n\n/** Retention bounds: `0` is meaningful (\"purge everything\"), so keep it. */\nfunction nonNegativeOrDefault(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? value\n : fallback;\n}\n\nfunction pickFiniteNumbers(\n args: Record<string, unknown>,\n keys: string[],\n): Record<string, number> {\n const picked: Record<string, number> = {};\n if (!args || typeof args !== 'object') {\n return picked;\n }\n for (const key of keys) {\n const value = args[key];\n if (typeof value === 'number' && Number.isFinite(value)) {\n picked[key] = value;\n }\n }\n return picked;\n}\n\nasync function deleteCounted(\n db: DatabaseInterface,\n condition: string,\n params: unknown[],\n table = '_smrt_field_usage_counters',\n): Promise<number> {\n const countRows = getQueryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM ${table} WHERE ${condition}`,\n ...params,\n ),\n );\n const total = numberFromRow(countRows[0] ?? {}, 'total');\n if (total > 0) {\n await db.query(`DELETE FROM ${table} WHERE ${condition}`, ...params);\n }\n return total;\n}\n\nfunction getQueryRows(result: unknown): Record<string, unknown>[] {\n return Array.isArray(result)\n ? (result as Record<string, unknown>[])\n : ((result as { rows?: Record<string, unknown>[] })?.rows ?? []);\n}\n\nfunction numberFromRow(row: Record<string, unknown>, key: string): number {\n const value = row[key];\n if (typeof value === 'number') {\n return value;\n }\n if (typeof value === 'bigint') {\n return Number(value);\n }\n if (typeof value === 'string') {\n return Number.parseFloat(value) || 0;\n }\n return 0;\n}\n","/**\n * Shared detection for OPTIONAL workspace dependencies\n * (`@happyvertical/smrt-users`, and since #2051 `@happyvertical/smrt-agents`\n * for the dormant learning schedules).\n *\n * A leaf module (no package-internal imports) so every dynamic-import seam —\n * the resolver's default tenant-hierarchy loader, the permission catalog\n * registration, and the schedule installer — shares one matcher without\n * creating an import cycle through the resolver/collection/model chain.\n */\n\n/** Node's missing-module message shapes, capturing the quoted specifier. */\nconst MISSING_MODULE_TARGET_PATTERN =\n /Cannot find (?:package|module) '([^']+)'/;\n\n/** Whether a missing-module TARGET specifier is `packageName` (or a subpath). */\nfunction isPackageSpecifier(target: string, packageName: string): boolean {\n return target === packageName || target.startsWith(`${packageName}/`);\n}\n\n/**\n * Whether an import failure means the named workspace package is simply not\n * installed (→ graceful degradation) rather than installed-but-broken\n * (→ rethrow, surfacing the problem instead of silently degrading).\n *\n * The decision is made on the missing-module TARGET parsed from Node's\n * `Cannot find package/module '<specifier>'` message (walking the full\n * `cause` chain): only a target that IS the package (or one of its subpaths)\n * counts. A transitive failure INSIDE an installed package names the other\n * module as the target — with the package path merely appearing as the\n * importer — and therefore rethrows. `importWorkspaceModule`'s own\n * source-fallback wrapper (`Failed to load <packageName> for ...`) is also\n * accepted: it is thrown only when the package itself cannot be located.\n *\n * Exported for direct testing; not re-exported from the package index.\n */\nexport function isMissingWorkspaceDependency(\n error: unknown,\n packageName: string,\n): boolean {\n let current: unknown = error;\n const seen = new Set<unknown>();\n\n while (current instanceof Error && !seen.has(current)) {\n seen.add(current);\n\n const match = current.message.match(MISSING_MODULE_TARGET_PATTERN);\n if (match && isPackageSpecifier(match[1], packageName)) {\n return true;\n }\n\n if (current.message.includes(`Failed to load ${packageName} for`)) {\n return true;\n }\n\n current = current.cause;\n }\n\n return false;\n}\n\n/** {@link isMissingWorkspaceDependency} for `@happyvertical/smrt-users`. */\nexport function isMissingUsersDependency(error: unknown): boolean {\n return isMissingWorkspaceDependency(error, '@happyvertical/smrt-users');\n}\n","/**\n * Dormant `AgentSchedule` installation for the #2051 learning loop.\n *\n * `@happyvertical/smrt-agents` is an OPTIONAL dependency of this package\n * (the smrt-users seam): agents sits ABOVE fields in the dependency DAG (it\n * hard-depends on smrt-users, ai, and secrets — exactly the packages fields\n * keeps optional), so the installer dynamic-imports it and degrades\n * gracefully (`installed: false`) when it is not present. The schedule TARGET\n * (`FieldUsageLearningAgent`) needs no agents import at all — smrt-jobs\n * resolves `agentType` through the `ObjectRegistry`.\n *\n * DORMANT BY DEFAULT (the epic's suggestion-first posture): schedules are\n * created with `enabled: false` / `status: 'disabled'` unless the caller\n * explicitly opts in with `enabled: true` (or later runs the AgentSchedule\n * `enable()` operator command / flips the row). Activation is a deliberate\n * per-deployment decision documented in this package's AGENTS.md.\n */\n\nimport { importWorkspaceModule } from '@happyvertical/smrt-core/utils/import-workspace-module';\nimport {\n getCurrentTenant,\n isSuperAdminBypass,\n TenantIsolationError,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { deterministicFieldsUuid } from './deterministic-id.js';\nimport type {\n FieldUsageMaintenanceConfig,\n FieldUsageSuggestionConfig,\n} from './usage-learning.js';\nimport { isMissingWorkspaceDependency } from './users-module.js';\n\n/** Registry-qualified schedule target (`AgentSchedule.agentType`). */\nexport const FIELD_USAGE_LEARNING_AGENT_TYPE =\n '@happyvertical/smrt-fields:FieldUsageLearningAgent';\n\n/** Method the aggregation/retention schedule invokes. */\nexport const FIELD_USAGE_MAINTENANCE_METHOD = 'runUsageMaintenance';\n\n/** Method the suggestion-generation schedule invokes. */\nexport const FIELD_USAGE_SUGGESTION_METHOD = 'runSuggestionGeneration';\n\n/**\n * Default cadence for counter maintenance: daily at 02:30 — in the SCHEDULER\n * HOST'S LOCAL TIME. See {@link ensureFieldUsageLearningSchedules} for why no\n * timezone can be selected here.\n */\nexport const DEFAULT_FIELD_USAGE_MAINTENANCE_CRON = '30 2 * * *';\n\n/** Default cadence for suggestion generation: weekly, Monday 03:00 host-local. */\nexport const DEFAULT_FIELD_USAGE_SUGGESTION_CRON = '0 3 * * 1';\n\n/**\n * Stable id for a global learning schedule, derived from the agent type and\n * method (the `TenantUsageMetric.recordUsage` precedent).\n *\n * `_smrt_agent_schedules` has no natural-key uniqueness on\n * `(agent_type, method)`, so a check-then-create would let two replicas\n * starting against an empty database each insert their own random-id row —\n * and with `enabled: true` every job would then run twice. A deterministic id\n * makes the insert converge on ONE primary key instead.\n */\nexport function fieldUsageScheduleId(method: string): Promise<string> {\n return deterministicFieldsUuid([\n 'field-usage-learning-schedule',\n FIELD_USAGE_LEARNING_AGENT_TYPE,\n method,\n ]);\n}\n\n/**\n * Structural surface of the agents module the installer consumes (no static\n * import — mirrors `FieldPolicyUsersModule`). `create`/`list`/`save` are the\n * standard SmrtCollection/SmrtObject shapes.\n */\nexport interface FieldUsageAgentsScheduleRow {\n id?: string | null;\n enabled?: boolean;\n /**\n * Owning tenant; `null`/absent marks the GLOBAL schedules this installer\n * manages. Read so the existence check cannot mistake a tenant-specific\n * schedule for the global one.\n */\n tenantId?: string | null;\n save?: () => Promise<unknown>;\n}\n\nexport interface FieldUsageAgentsModule {\n AgentScheduleCollection: {\n create(options: { db: DatabaseInterface }): Promise<{\n list(options: {\n where: Record<string, unknown>;\n }): Promise<FieldUsageAgentsScheduleRow[]>;\n create(\n data: Record<string, unknown>,\n ): Promise<FieldUsageAgentsScheduleRow>;\n }>;\n };\n}\n\nexport interface EnsureFieldUsageLearningSchedulesOptions {\n db: DatabaseInterface;\n /**\n * Whether the schedules start enabled. DEFAULT FALSE — the learning loop\n * ships dormant; enabling it is an explicit deployment opt-in.\n */\n enabled?: boolean;\n maintenanceCron?: string;\n suggestionCron?: string;\n /** Threshold/retention overrides persisted into the schedules' methodArgs. */\n maintenanceArgs?: Partial<FieldUsageMaintenanceConfig>;\n suggestionArgs?: Partial<FieldUsageSuggestionConfig>;\n /** Injection seam for tests / hosts that already loaded the agents module. */\n agentsModule?: FieldUsageAgentsModule;\n}\n\nexport interface EnsureFieldUsageLearningSchedulesResult {\n /** False when `@happyvertical/smrt-agents` is not installed (no-op). */\n installed: boolean;\n /** Schedules created by THIS call (existing rows are left untouched). */\n created: number;\n}\n\n/**\n * Idempotently create the two GLOBAL (tenant-null) `AgentSchedule` rows for\n * the learning loop — the aggregation/retention roll-up and the\n * suggestion-generation job. An existing GLOBAL row for the agent type +\n * method is never modified (operator state like enable/disable is preserved).\n *\n * The existence check is scoped to the GLOBAL rows deliberately: a deployment\n * may also run tenant-specific schedules for the same agent type and method\n * (the ambient-context runs the jobs support), and matching one of those would\n * silently skip installing the global schedule this function promises. Tenant\n * rows are read but never touched.\n *\n * Concurrency: each schedule is written under a DETERMINISTIC id\n * ({@link fieldUsageScheduleId}) with insert-only semantics, so two replicas\n * installing at once converge on one row — the pre-check is only the cheap\n * path, and a primary-key collision is treated as \"already installed\" rather\n * than an error. Insert-only also means an existing row's operator state\n * (enabled/disabled, edited cron) is never overwritten by a later install.\n *\n * **No timezone option, deliberately.** `AgentSchedule` carries a `timezone`\n * column, but `getNextCronDate(cron, _timezone)` ignores the argument and\n * matches against host-local `getHours()`/`getDate()`, and smrt-jobs'\n * `ScheduleRunner` recalculates with the same host-local parser. Accepting a\n * timezone here would advertise control this stack does not have, so these\n * schedules fire in the SCHEDULER HOST'S LOCAL TIME — pick crons accordingly\n * (see `agents/usage-learning.md`). Fixing the agents-side parser is out of\n * scope for this package.\n *\n * System operation: global schedules are platform state, so a non-bypass\n * ambient tenant context is rejected (fail closed) — call this from trusted\n * startup/migration code.\n */\nexport async function ensureFieldUsageLearningSchedules(\n options: EnsureFieldUsageLearningSchedulesOptions,\n): Promise<EnsureFieldUsageLearningSchedulesResult> {\n const context = getCurrentTenant();\n if (context && !isSuperAdminBypass()) {\n throw new TenantIsolationError(\n 'ensureFieldUsageLearningSchedules installs GLOBAL schedules and must ' +\n 'run from trusted execution (no ambient tenant context, or ' +\n 'super-admin bypass)',\n { tenantId: context.tenantId },\n );\n }\n\n let agentsModule = options.agentsModule;\n if (!agentsModule) {\n try {\n agentsModule = await importWorkspaceModule<FieldUsageAgentsModule>({\n packageName: '@happyvertical/smrt-agents',\n sourceEntry: 'packages/agents/src/index.ts',\n purpose: 'field usage learning schedule installation',\n });\n } catch (error) {\n if (isMissingWorkspaceDependency(error, '@happyvertical/smrt-agents')) {\n return { installed: false, created: 0 };\n }\n throw error;\n }\n }\n\n const schedules = await agentsModule.AgentScheduleCollection.create({\n db: options.db,\n });\n const enabled = options.enabled ?? false;\n let created = 0;\n\n const definitions = [\n {\n method: FIELD_USAGE_MAINTENANCE_METHOD,\n cron: options.maintenanceCron ?? DEFAULT_FIELD_USAGE_MAINTENANCE_CRON,\n methodArgs: options.maintenanceArgs ?? {},\n },\n {\n method: FIELD_USAGE_SUGGESTION_METHOD,\n cron: options.suggestionCron ?? DEFAULT_FIELD_USAGE_SUGGESTION_CRON,\n methodArgs: options.suggestionArgs ?? {},\n },\n ];\n\n for (const definition of definitions) {\n const existing = await schedules.list({\n where: {\n agentType: FIELD_USAGE_LEARNING_AGENT_TYPE,\n method: definition.method,\n },\n });\n // Filter to the GLOBAL scope in memory rather than adding\n // `tenantId: null` to the where clause: an explicit `tenant_id IS NULL`\n // filter is what the tenancy interceptor flags as an isolation violation\n // (the reason `queryGlobal` exists), and this must also work under the\n // bypass context the guard above allows.\n if (existing.some(isGlobalScheduleRow)) {\n continue;\n }\n\n const id = await fieldUsageScheduleId(definition.method);\n let row: FieldUsageAgentsScheduleRow;\n try {\n row = await schedules.create({\n id,\n tenantId: null,\n agentType: FIELD_USAGE_LEARNING_AGENT_TYPE,\n agentId: null,\n cron: definition.cron,\n method: definition.method,\n agentConfig: {},\n methodArgs: definition.methodArgs,\n enabled,\n status: enabled ? 'active' : 'disabled',\n // Strict insert: a concurrent replica that already created this row\n // must NOT be adopted-and-overwritten (that would resurrect a\n // deliberately disabled schedule or clobber an edited cron).\n _insertOnly: true,\n });\n } catch (error) {\n // ONLY a confirmed collision may be swallowed. Constraint-error text is\n // not portable across sqlite/PostgreSQL/DuckDB (and core wraps driver\n // errors), so the evidence is the ROW ITSELF: re-read the deterministic\n // id and treat \"it exists now\" as proof another installer won the race.\n // Anything else — schema drift, validation, a dropped connection — must\n // propagate, or the installer would report success with the schedule\n // missing.\n const raced = await schedules.list({ where: { id } });\n if (raced.length === 0) {\n throw error;\n }\n continue;\n }\n // The schedulePersonaInstance precedent: an explicit save() runs the\n // model's beforeSave (next-run calculation) even if create() already\n // persisted the row.\n await row.save?.();\n created += 1;\n }\n\n return { installed: true, created };\n}\n\n/** Whether a schedule row is one of the GLOBAL (tenant-null) rows. */\nfunction isGlobalScheduleRow(row: FieldUsageAgentsScheduleRow): boolean {\n return row.tenantId === null || row.tenantId === undefined;\n}\n","/**\n * @happyvertical/smrt-fields\n *\n * Layered field policy store and resolver for SMRT objects (epic #2045):\n * per-field defaults, visibility tiers, help text, labels, ordering, and org\n * locks, personalized at app, tenant, and user scope over the code seed.\n *\n * @packageDocumentation\n */\n\n// Self-register this package's manifest before any @smrt() decorator fires\n// downstream. Must come first so the side effect runs ahead of the class\n// module loads below. See __smrt-register__.ts for issue #1132 context.\nimport './__smrt-register__.js';\n\nimport { ensureFieldPolicyPermissionsRegistered } from './permissions.js';\n\nexport {\n clearFieldPolicyCache,\n getFieldPolicyCacheTtlMs,\n invalidateFieldPolicyCache,\n} from './cache.js';\nexport { FieldPolicyCollection } from './collections/FieldPolicyCollection.js';\nexport {\n FieldPolicySuggestionCollection,\n FieldPolicySuggestionConflictError,\n} from './collections/FieldPolicySuggestionCollection.js';\nexport {\n decodeHistogramKey,\n FieldUsageCounterCollection,\n isHistogramEligibleField,\n MAX_USAGE_REPORT_ENTRIES,\n serializeHistogramSample,\n} from './collections/FieldUsageCounterCollection.js';\nexport {\n applyFieldPolicyToDataSurface,\n type FieldPolicyDataSurfaceOptions,\n policyToDataSurfaceDescriptor,\n} from './data-surface.js';\nexport {\n assertDefaultValueMatchesFieldType,\n buildCodeSeedDelta,\n buildCodeSeedVisibility,\n type FieldDefinitionMap,\n getCodeDefault,\n getCodeSeedGroup,\n getFieldReadPermission,\n getObjectFieldMap,\n isPolicyAddressableField,\n isRequiredField,\n isSensitiveField,\n isStorableReferenceId,\n isTransientField,\n isUsableRequiredDefault,\n type RegisteredFieldInfo,\n requireRegisteredObject,\n sanitizeFieldUIHints,\n} from './field-definitions.js';\nexport {\n resolveFieldPolicy,\n resolveFieldPolicyExplained,\n resolveSurvivingTenantChainIds,\n} from './field-policy-resolver.js';\nexport { FieldPolicy } from './models/FieldPolicy.js';\nexport {\n ACTIVE_SUGGESTION_KEY,\n FieldPolicySuggestion,\n} from './models/FieldPolicySuggestion.js';\nexport {\n FieldUsageCounter,\n fieldUsagePeriodForDate,\n MAX_DISTINCT_USERS_PER_BUCKET,\n MAX_VALUE_HISTOGRAM_BUCKETS,\n} from './models/FieldUsageCounter.js';\nexport {\n ensureFieldPolicyPermissionsRegistered,\n FIELD_POLICY_PERMISSION_DEFINITIONS,\n MANAGE_FIELD_POLICY_PERMISSION,\n PERSONALIZE_FIELD_POLICY_PERMISSION,\n} from './permissions.js';\nexport {\n type BuildFieldPolicySettingsCatalogOptions,\n buildFieldPolicySettingsCatalog,\n type FieldPolicyCatalogField,\n type FieldPolicyCatalogObjectSummary,\n type FieldPolicyDetailItem,\n type FieldPolicySettingsCatalogData,\n type FieldPolicySettingsCatalogPage,\n type FieldPolicySettingsCatalogQuery,\n type FieldPolicySummaryItem,\n fieldPolicyCatalogItemId,\n parseFieldPolicyCatalogQuery,\n} from './settings-catalog.js';\nexport {\n type AcceptFieldPolicySuggestionResult,\n APP_FIELD_POLICY_SCOPE_KEY,\n type DismissFieldPolicySuggestionResult,\n type ExplainedObjectFieldPolicy,\n FIELD_POLICY_SCOPE_TYPES,\n FIELD_POLICY_VISIBILITIES,\n type FieldPolicyAuditRow,\n type FieldPolicyAuditSnapshot,\n type FieldPolicyBatchResult,\n type FieldPolicyDelta,\n type FieldPolicyDriftReason,\n type FieldPolicyDriftRow,\n type FieldPolicyEditorCapabilities,\n type FieldPolicyEditorRow,\n type FieldPolicyEditorState,\n type FieldPolicyEditorStateDenied,\n type FieldPolicyEditorStateResult,\n type FieldPolicyLayerContribution,\n type FieldPolicyOptions,\n type FieldPolicyScopeType,\n type FieldPolicySuggestionData,\n type FieldPolicySuggestionKind,\n type FieldPolicySuggestionStatus,\n type FieldPolicyTenantHierarchyLoader,\n type FieldPolicyTenantHierarchyProvider,\n type FieldPolicyTenantNode,\n type FieldPolicyUsersModule,\n type FieldPolicyUsersTenantRecord,\n type FieldPolicyVisibility,\n type FieldUsageReportEntry,\n type FieldUsageReportResult,\n type PendingFieldPolicySuggestionsResult,\n type ResolvedFieldPolicy,\n type ResolvedObjectFieldPolicy,\n type ResolveFieldPolicyOptions,\n} from './types.js';\nexport {\n FieldUsageLearningAgent,\n pruneFieldPolicySuggestions,\n pruneFieldUsageCounters,\n runFieldPolicySuggestionGeneration,\n runFieldUsageMaintenance,\n} from './usage-learning.js';\nexport {\n ensureFieldUsageLearningSchedules,\n FIELD_USAGE_LEARNING_AGENT_TYPE,\n} from './usage-schedules.js';\n\n// Contribute the field-policy capabilities to the shared runtime catalog on\n// import, so normal role seeding and every server gate recognize the slugs.\nensureFieldPolicyPermissionsRegistered();\n"],"mappings":"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"}