@happyvertical/smrt-fields 0.40.61 → 0.40.63
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/AGENTS.md +18 -0
- package/README.md +125 -112
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js +1218 -0
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js.map +1 -0
- package/dist/collections/FieldPolicySuggestionCollection.d.ts +160 -0
- package/dist/collections/FieldPolicySuggestionCollection.d.ts.map +1 -0
- package/dist/collections/FieldUsageCounterCollection.d.ts +143 -0
- package/dist/collections/FieldUsageCounterCollection.d.ts.map +1 -0
- package/dist/deterministic-id.d.ts +18 -0
- package/dist/deterministic-id.d.ts.map +1 -0
- package/dist/field-definitions.d.ts +8 -0
- package/dist/field-definitions.d.ts.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1565 -1077
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1442 -188
- package/dist/models/FieldPolicySuggestion.d.ts +132 -0
- package/dist/models/FieldPolicySuggestion.d.ts.map +1 -0
- package/dist/models/FieldUsageCounter.d.ts +178 -0
- package/dist/models/FieldUsageCounter.d.ts.map +1 -0
- package/dist/models/FieldUsageReportReceipt.d.ts +25 -0
- package/dist/models/FieldUsageReportReceipt.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +404 -7
- package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +54 -0
- package/dist/svelte/__tests__/FormHelp.test.js +8 -0
- package/dist/svelte/__tests__/ObjectForm.test.js +131 -0
- package/dist/svelte/__tests__/UsageLearning.test.js +140 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +21 -4
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +5 -1
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -1
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte +23 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/playground.test.js +66 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte +28 -1
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +3 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -1
- package/dist/svelte/components/FieldPolicyGearButton.svelte +8 -1
- package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -1
- package/dist/svelte/components/FieldPolicyGearProvider.svelte +34 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +3 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -1
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte +135 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts +11 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FormHelp.svelte +16 -1
- package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ObjectForm.svelte +91 -2
- package/dist/svelte/components/ObjectForm.svelte.d.ts +11 -1
- package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -1
- package/dist/svelte/context.svelte.d.ts +15 -0
- package/dist/svelte/context.svelte.d.ts.map +1 -1
- package/dist/svelte/context.svelte.js +15 -0
- package/dist/svelte/gear-context.svelte.d.ts +2 -0
- package/dist/svelte/gear-context.svelte.d.ts.map +1 -1
- package/dist/svelte/index.d.ts +6 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +4 -1
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte +18 -12
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -1
- package/dist/svelte/suggestions.d.ts +38 -0
- package/dist/svelte/suggestions.d.ts.map +1 -0
- package/dist/svelte/suggestions.js +68 -0
- package/dist/svelte/usage-capture.d.ts +63 -0
- package/dist/svelte/usage-capture.d.ts.map +1 -0
- package/dist/svelte/usage-capture.js +61 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/dist/usage-learning.d.ts +201 -0
- package/dist/usage-learning.d.ts.map +1 -0
- package/dist/usage-schedules.d.ts +110 -0
- package/dist/usage-schedules.d.ts.map +1 -0
- package/dist/users-module.d.ts +30 -0
- package/dist/users-module.d.ts.map +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
|
+
import { FieldPolicySuggestionData, FieldPolicySuggestionKind, FieldPolicySuggestionStatus } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `activeKey` sentinel for the single ACTIVE (pending) suggestion per
|
|
5
|
+
* `(objectRef, fieldName, tenantId, kind)`. Settled rows key themselves by id,
|
|
6
|
+
* so history never competes for the slot.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ACTIVE_SUGGESTION_KEY = "active";
|
|
9
|
+
export interface FieldPolicySuggestionOptions extends SmrtObjectOptions {
|
|
10
|
+
objectRef?: string;
|
|
11
|
+
fieldName?: string;
|
|
12
|
+
tenantId?: string;
|
|
13
|
+
kind?: FieldPolicySuggestionKind;
|
|
14
|
+
proposedValue?: string | null;
|
|
15
|
+
evidence?: string;
|
|
16
|
+
status?: FieldPolicySuggestionStatus;
|
|
17
|
+
cooldownUntil?: Date | null;
|
|
18
|
+
decidedBy?: string | null;
|
|
19
|
+
decidedAt?: Date | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A pending, human-reviewable field-policy improvement proposed from real
|
|
23
|
+
* usage (epic #2045, issue #2051): promote a field to the `basic` tier, or
|
|
24
|
+
* seed an org default with the dominant observed value.
|
|
25
|
+
*
|
|
26
|
+
* Suggestion-first by design — a row DOES NOTHING until a
|
|
27
|
+
* `fields.policy.manage` holder accepts it, and acceptance writes the
|
|
28
|
+
* org-scope {@link ../models/FieldPolicy.FieldPolicy} row through NORMAL
|
|
29
|
+
* validation (registry check, type check, security rail, required-field
|
|
30
|
+
* invariant, ownership + permission split). Dismissing sets a cool-down that
|
|
31
|
+
* suppresses regeneration of the same suggestion.
|
|
32
|
+
*
|
|
33
|
+
* **One ACTIVE suggestion per identity, structurally** (not merely by a
|
|
34
|
+
* check-then-insert): {@link activeKey} is a computed column holding the
|
|
35
|
+
* sentinel {@link ACTIVE_SUGGESTION_KEY} while the row is `pending` and the
|
|
36
|
+
* row's own id once it settles, and it participates in `conflictColumns`. The
|
|
37
|
+
* unique index therefore admits at most ONE pending row per
|
|
38
|
+
* `(objectRef, fieldName, tenantId, kind)` while every settled row keys
|
|
39
|
+
* itself — so two overlapping generation runs (e.g. a global and a
|
|
40
|
+
* tenant-specific schedule) UPSERT onto the same row instead of duplicating,
|
|
41
|
+
* with no transaction spanning their reads. It is the `FieldPolicy.scopeKey`
|
|
42
|
+
* trick applied to a lifecycle slot. On settle the column flips to the row's
|
|
43
|
+
* id, which frees the slot for a post-cool-down regeneration while keeping the
|
|
44
|
+
* dismissed/accepted history (and its id) intact — core conflicts a persisted
|
|
45
|
+
* row on its primary key (#1472), so the flip is a plain UPDATE.
|
|
46
|
+
*
|
|
47
|
+
* #1885 seam: this substrate is fully independent of personas'
|
|
48
|
+
* `DirectiveProposal` review queue (no shared producer discriminator —
|
|
49
|
+
* deliberately out of scope). Tenant learning agents MAY create
|
|
50
|
+
* FieldPolicySuggestion rows through this model's normal validation; the
|
|
51
|
+
* reviewed `fields.policy.manage` acceptance gate is unchanged by who
|
|
52
|
+
* proposed.
|
|
53
|
+
*/
|
|
54
|
+
export declare class FieldPolicySuggestion extends SmrtObject {
|
|
55
|
+
/** Qualified class name of the target object (`@package/name:ClassName`). */
|
|
56
|
+
objectRef: string;
|
|
57
|
+
/** Field name on the target object (validated against the registry). */
|
|
58
|
+
fieldName: string;
|
|
59
|
+
/** Owning tenant (required — suggestions always target one org). */
|
|
60
|
+
tenantId?: string;
|
|
61
|
+
/** What the suggestion proposes ('promote' | 'default'). */
|
|
62
|
+
kind: FieldPolicySuggestionKind;
|
|
63
|
+
/**
|
|
64
|
+
* JSON-encoded proposed default (`kind: 'default'` only) — the exact
|
|
65
|
+
* encoding `FieldPolicy.defaultValue` stores, so acceptance passes it
|
|
66
|
+
* through unchanged. NULL for `promote`.
|
|
67
|
+
*/
|
|
68
|
+
proposedValue: string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Human-readable evidence as a JSON string: a `summary` sentence plus the
|
|
71
|
+
* structured window/threshold numbers behind it (see
|
|
72
|
+
* `buildFieldUsageEvidence`).
|
|
73
|
+
*/
|
|
74
|
+
evidence: string;
|
|
75
|
+
/** Lifecycle status ('pending' | 'accepted' | 'dismissed'). */
|
|
76
|
+
status: FieldPolicySuggestionStatus;
|
|
77
|
+
/**
|
|
78
|
+
* Computed lifecycle-slot key, set in `save()`: {@link ACTIVE_SUGGESTION_KEY}
|
|
79
|
+
* while `pending`, else the row's own id. It exists ONLY to make the
|
|
80
|
+
* `conflictColumns` unique index express "at most one ACTIVE suggestion per
|
|
81
|
+
* identity, unlimited settled history" (the `FieldPolicy.scopeKey`
|
|
82
|
+
* precedent) — never read it for logic; `status` owns that.
|
|
83
|
+
*/
|
|
84
|
+
activeKey: string;
|
|
85
|
+
/**
|
|
86
|
+
* Until this instant, a dismissed suggestion suppresses regeneration of the
|
|
87
|
+
* same `(objectRef, fieldName, tenantId, kind)` suggestion. NULL until
|
|
88
|
+
* dismissed.
|
|
89
|
+
*/
|
|
90
|
+
cooldownUntil: Date | null;
|
|
91
|
+
/** Who accepted/dismissed (audit attribution, #2050); not validated. */
|
|
92
|
+
decidedBy: string | null;
|
|
93
|
+
/** When the suggestion was accepted/dismissed. */
|
|
94
|
+
decidedAt: Date | null;
|
|
95
|
+
constructor(options?: FieldPolicySuggestionOptions);
|
|
96
|
+
/** Parse the stored evidence object (guarded; junk parses as empty). */
|
|
97
|
+
getEvidence(): Record<string, unknown>;
|
|
98
|
+
/** Serialize an evidence object into the stored JSON string. */
|
|
99
|
+
setEvidence(evidence: Record<string, unknown>): void;
|
|
100
|
+
/** Parse the proposed value; `undefined` when none is stored. */
|
|
101
|
+
getProposedValue(): unknown;
|
|
102
|
+
/** Serialized row shape for the collection actions. */
|
|
103
|
+
toSuggestionData(): FieldPolicySuggestionData;
|
|
104
|
+
save(): Promise<this>;
|
|
105
|
+
/**
|
|
106
|
+
* Recompute the lifecycle-slot key: the shared sentinel while pending (so
|
|
107
|
+
* the unique index admits exactly one), the row's own id once settled (so
|
|
108
|
+
* history never competes for the slot and the freed slot allows a
|
|
109
|
+
* post-cool-down regeneration). A settled row that has not been persisted
|
|
110
|
+
* yet is assigned its id here — the key must be unique from the first write.
|
|
111
|
+
*/
|
|
112
|
+
private applyActiveKey;
|
|
113
|
+
delete(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* The "normal validation" the #1885 seam promises producers: registry-known
|
|
116
|
+
* field, policy-addressable, never sensitive/read-permission-gated/transient
|
|
117
|
+
* (those fields are count-only in usage data and get no suggestions), valid
|
|
118
|
+
* kind/status, and for `default` suggestions a JSON proposed value that
|
|
119
|
+
* type-checks against the manifest field type.
|
|
120
|
+
*/
|
|
121
|
+
private validateFieldPolicySuggestion;
|
|
122
|
+
/**
|
|
123
|
+
* Tenant write boundary (the FieldPolicy posture): inside a non-bypass
|
|
124
|
+
* tenant context a caller may only touch its own tenant's suggestions —
|
|
125
|
+
* checked against BOTH the in-memory scope and, for persisted rows, the
|
|
126
|
+
* PERSISTED tenant (a foreign row cannot be re-scoped into the caller's
|
|
127
|
+
* tenant). Trusted execution (no context / bypass) is exempt — that is what
|
|
128
|
+
* lets the scheduled generation job and platform flows operate.
|
|
129
|
+
*/
|
|
130
|
+
private assertRowOwnedByAmbientContext;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=FieldPolicySuggestion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldPolicySuggestion.d.ts","sourceRoot":"","sources":["../../src/models/FieldPolicySuggestion.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAclC,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EACjC,MAAM,aAAa,CAAC;AAErB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,MAAM,WAAW,4BAA6B,SAAQ,iBAAiB;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,yBAAyB,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,2BAA2B,CAAC;IACrC,aAAa,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAOH,qBAaa,qBAAsB,SAAQ,UAAU;IACnD,6EAA6E;IAE7E,SAAS,EAAE,MAAM,CAAM;IAEvB,wEAAwE;IAExE,SAAS,EAAE,MAAM,CAAM;IAEvB,oEAAoE;IAEpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,4DAA4D;IAE5D,IAAI,EAAE,yBAAyB,CAAa;IAE5C;;;;OAIG;IAEH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC;;;;OAIG;IAEH,QAAQ,EAAE,MAAM,CAAQ;IAExB,+DAA+D;IAE/D,MAAM,EAAE,2BAA2B,CAAa;IAEhD;;;;;;OAMG;IAEH,SAAS,EAAE,MAAM,CAAyB;IAE1C;;;;OAIG;IAEH,aAAa,EAAE,IAAI,GAAG,IAAI,CAAQ;IAElC,wEAAwE;IAExE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEhC,kDAAkD;IAElD,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;gBAElB,OAAO,GAAE,4BAAiC;IAkBtD,wEAAwE;IACxE,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAWtC,gEAAgE;IAChE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIpD,iEAAiE;IACjE,gBAAgB,IAAI,OAAO;IAW3B,uDAAuD;IACvD,gBAAgB,IAAI,yBAAyB;IAgB9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAWP,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtC;;;;;;OAMG;YACW,6BAA6B;IAiF3C;;;;;;;OAOG;YACW,8BAA8B;CAyC7C"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Cap on distinct-user ids stored per bucket. For threshold questions
|
|
4
|
+
* ("did at least N distinct users set this field?") the capped set is EXACT up
|
|
5
|
+
* to the cap; once overflowed, {@link FieldUsageCounter.distinctUserCount} is
|
|
6
|
+
* an honest LOWER BOUND that trivially satisfies any threshold ≤ the cap.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_DISTINCT_USERS_PER_BUCKET = 100;
|
|
9
|
+
/** Cap on histogram buckets per counter row (bounded storage). */
|
|
10
|
+
export declare const MAX_VALUE_HISTOGRAM_BUCKETS = 25;
|
|
11
|
+
/** Longest histogram key recorded; longer samples are skipped (count-only). */
|
|
12
|
+
export declare const MAX_VALUE_HISTOGRAM_KEY_LENGTH = 64;
|
|
13
|
+
/** `period` bucket format: UTC calendar day. */
|
|
14
|
+
export declare const FIELD_USAGE_PERIOD_PATTERN: RegExp;
|
|
15
|
+
/** The UTC day bucket for a timestamp (`YYYY-MM-DD`). */
|
|
16
|
+
export declare function fieldUsagePeriodForDate(date: Date): string;
|
|
17
|
+
/**
|
|
18
|
+
* A prototype-free histogram map.
|
|
19
|
+
*
|
|
20
|
+
* Histogram keys are user-supplied values (an `idType: 'text'` reference id may
|
|
21
|
+
* legitimately be `constructor`, `toString`, or `__proto__`). On a plain object
|
|
22
|
+
* those either resolve to inherited members — making an absent bucket look
|
|
23
|
+
* present and corrupting its count — or, for `__proto__`, invoke the prototype
|
|
24
|
+
* setter instead of creating an own key. A null-prototype object has no such
|
|
25
|
+
* members, so every key behaves like data. Use this everywhere histogram counts
|
|
26
|
+
* are accumulated (storage AND merge paths).
|
|
27
|
+
*/
|
|
28
|
+
export declare function emptyHistogram(): Record<string, number>;
|
|
29
|
+
export interface FieldUsageCounterOptions extends SmrtObjectOptions {
|
|
30
|
+
objectRef?: string;
|
|
31
|
+
fieldName?: string;
|
|
32
|
+
tenantId?: string;
|
|
33
|
+
period?: string;
|
|
34
|
+
submissionCount?: number;
|
|
35
|
+
setCount?: number;
|
|
36
|
+
distinctUserCount?: number;
|
|
37
|
+
distinctUserIds?: string;
|
|
38
|
+
distinctUsersOverflowed?: boolean;
|
|
39
|
+
valueHistogram?: string | null;
|
|
40
|
+
valueHistogramOverflowed?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Period-bucketed field usage counter (epic #2045, issue #2051).
|
|
44
|
+
*
|
|
45
|
+
* One row aggregates field submissions for a single
|
|
46
|
+
* `(objectRef, fieldName, tenantId, period)` — the substrate the
|
|
47
|
+
* suggestion-generation job reads. Counters are deliberately APPROXIMATE:
|
|
48
|
+
* ingestion is fire-and-forget and concurrent bucket merges may lose an
|
|
49
|
+
* increment (read-modify-write), which is acceptable for usage statistics and
|
|
50
|
+
* documented here rather than papered over.
|
|
51
|
+
*
|
|
52
|
+
* TWO counters, because they answer different questions:
|
|
53
|
+
* - {@link submissionCount} — EVERY observed submission of the field
|
|
54
|
+
* (default-matching or not). It is the denominator for value dominance:
|
|
55
|
+
* "N% of submissions used value V". Without it, a value seen only in
|
|
56
|
+
* deviations would look 100% dominant even against thousands of
|
|
57
|
+
* default-valued submissions.
|
|
58
|
+
* - {@link setCount} — submissions whose value DIFFERED from the resolved
|
|
59
|
+
* default (server-derived). It plus {@link distinctUserIds} is the
|
|
60
|
+
* promote signal ("real users are actively filling this in").
|
|
61
|
+
*
|
|
62
|
+
* Content rails (enforced by the ingestion action, which derives everything
|
|
63
|
+
* from the live registry and never trusts the client):
|
|
64
|
+
* - Sensitive and read-permission-gated fields are COUNT-ONLY: their raw
|
|
65
|
+
* values are never recorded anywhere in usage data — not even for
|
|
66
|
+
* default-matching submissions.
|
|
67
|
+
* - Value histograms exist only for low-cardinality field types (`boolean`,
|
|
68
|
+
* `foreignKey`, `crossPackageRef`) — never free text, even non-sensitive
|
|
69
|
+
* text (PII risk) — with a bounded bucket count and key length. They cover
|
|
70
|
+
* ALL submissions (not just deviations) so the dominance ratio is a true
|
|
71
|
+
* fraction of {@link submissionCount}.
|
|
72
|
+
* - `distinctUserIds` is a capped set with an overflow marker (see
|
|
73
|
+
* {@link MAX_DISTINCT_USERS_PER_BUCKET} for the honesty contract).
|
|
74
|
+
*/
|
|
75
|
+
export declare class FieldUsageCounter extends SmrtObject {
|
|
76
|
+
/** Qualified class name of the target object (`@package/name:ClassName`). */
|
|
77
|
+
objectRef: string;
|
|
78
|
+
/** Field name on the target object. */
|
|
79
|
+
fieldName: string;
|
|
80
|
+
/**
|
|
81
|
+
* Owning tenant. REQUIRED: ingestion fails closed without an ambient tenant
|
|
82
|
+
* context, so every row is attributable (and the conflict-column tuple
|
|
83
|
+
* stays total). Native UUID on PostgreSQL/DuckDB.
|
|
84
|
+
*/
|
|
85
|
+
tenantId?: string;
|
|
86
|
+
/** UTC day bucket (`YYYY-MM-DD`); lexicographic order is time order. */
|
|
87
|
+
period: string;
|
|
88
|
+
/**
|
|
89
|
+
* EVERY observed submission of this field in the bucket, whether or not the
|
|
90
|
+
* value matched the resolved default — the dominance denominator.
|
|
91
|
+
*
|
|
92
|
+
* Rows written before this column existed carry `0` while `setCount > 0`;
|
|
93
|
+
* {@link isLegacyBucket} detects that shape and the generation job then
|
|
94
|
+
* treats the total as UNKNOWN and skips `default` suggestions for the group
|
|
95
|
+
* (promote, which needs no denominator, still works).
|
|
96
|
+
*/
|
|
97
|
+
submissionCount: number;
|
|
98
|
+
/**
|
|
99
|
+
* Submissions whose value DIFFERED from the server-resolved default (the
|
|
100
|
+
* promote signal). Always `<= submissionCount` on rows written by the
|
|
101
|
+
* current ingestion path.
|
|
102
|
+
*/
|
|
103
|
+
setCount: number;
|
|
104
|
+
/**
|
|
105
|
+
* Size of the stored distinct-user set. When
|
|
106
|
+
* {@link distinctUsersOverflowed} is true this is a LOWER BOUND (the set is
|
|
107
|
+
* capped), never an estimate.
|
|
108
|
+
*/
|
|
109
|
+
distinctUserCount: number;
|
|
110
|
+
/** JSON array of distinct user ids, capped (see the class doc). */
|
|
111
|
+
distinctUserIds: string;
|
|
112
|
+
/** True once a distinct user was NOT added because the set is at its cap. */
|
|
113
|
+
distinctUsersOverflowed: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* JSON object `serializedValue -> count` for histogram-eligible fields;
|
|
116
|
+
* NULL when the field is count-only. Keys are bounded in number and length.
|
|
117
|
+
*/
|
|
118
|
+
valueHistogram: string | null;
|
|
119
|
+
/** True once a sample was dropped because the bucket cap was reached. */
|
|
120
|
+
valueHistogramOverflowed: boolean;
|
|
121
|
+
constructor(options?: FieldUsageCounterOptions);
|
|
122
|
+
/** Parse the stored distinct-user set (guarded; junk parses as empty). */
|
|
123
|
+
getDistinctUserIds(): string[];
|
|
124
|
+
/**
|
|
125
|
+
* Add a user to the distinct set, honoring the cap. At the cap the id is
|
|
126
|
+
* NOT added and the overflow marker is set instead, keeping
|
|
127
|
+
* {@link distinctUserCount} an honest lower bound.
|
|
128
|
+
*/
|
|
129
|
+
addDistinctUser(userId: string): void;
|
|
130
|
+
/**
|
|
131
|
+
* Parse the stored histogram (guarded; junk parses as empty).
|
|
132
|
+
*
|
|
133
|
+
* Returns a NULL-PROTOTYPE object. Histogram keys are user-supplied ids —
|
|
134
|
+
* an `idType: 'text'` reference may legitimately be `constructor`,
|
|
135
|
+
* `toString`, or `__proto__` — and on a plain object those inherit truthy
|
|
136
|
+
* prototype values (so a missing bucket reads as present) or, for
|
|
137
|
+
* `__proto__`, hit the prototype setter instead of creating an own key.
|
|
138
|
+
* Both would silently corrupt counts. See {@link emptyHistogram}.
|
|
139
|
+
*/
|
|
140
|
+
getValueHistogram(): Record<string, number>;
|
|
141
|
+
/**
|
|
142
|
+
* Record one histogram sample under an already-serialized key, honoring the
|
|
143
|
+
* bucket cap (a NEW key past the cap is dropped and the overflow marker
|
|
144
|
+
* set; existing keys keep counting).
|
|
145
|
+
*
|
|
146
|
+
* Bucket presence is an OWN-key test, never a truthiness/`undefined` read,
|
|
147
|
+
* so prototype-shaped ids behave like any other key.
|
|
148
|
+
*/
|
|
149
|
+
recordHistogramSample(key: string): void;
|
|
150
|
+
/**
|
|
151
|
+
* Whether this bucket predates the {@link submissionCount} column (or was
|
|
152
|
+
* corrupted): it records deviations without a total, so no honest dominance
|
|
153
|
+
* ratio can be computed from it. The generation job skips `default`
|
|
154
|
+
* suggestions for any group containing such a bucket.
|
|
155
|
+
*/
|
|
156
|
+
isLegacyBucket(): boolean;
|
|
157
|
+
save(): Promise<this>;
|
|
158
|
+
delete(): Promise<void>;
|
|
159
|
+
private validateFieldUsageCounter;
|
|
160
|
+
/**
|
|
161
|
+
* Tenant write boundary (the FieldPolicy posture — no class-level
|
|
162
|
+
* `@TenantScoped`, because the learning jobs legitimately operate
|
|
163
|
+
* cross-tenant in trusted execution): inside a non-bypass tenant context a
|
|
164
|
+
* caller may only touch rows of its own tenant; without a context (system/
|
|
165
|
+
* job execution) writes are trusted.
|
|
166
|
+
*
|
|
167
|
+
* Checked against BOTH the in-memory tenant and — for a row that already
|
|
168
|
+
* exists — the PERSISTED one. The persisted check is what makes the boundary
|
|
169
|
+
* real: bucket ids are deterministic and the deriving helper is exported, so
|
|
170
|
+
* a foreign row is trivially addressable, and an in-memory-only check would
|
|
171
|
+
* let a caller load it, re-stamp `tenantId` with its own, and adopt or delete
|
|
172
|
+
* another tenant's counters (the #2047 FieldPolicy pattern).
|
|
173
|
+
*/
|
|
174
|
+
private assertRowOwnedByAmbientContext;
|
|
175
|
+
/** The stored tenant for this row's id; `null` when it is not persisted. */
|
|
176
|
+
private getPersistedTenantId;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=FieldUsageCounter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldUsageCounter.d.ts","sourceRoot":"","sources":["../../src/models/FieldUsageCounter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAQlC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD,kEAAkE;AAClE,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,+EAA+E;AAC/E,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD,gDAAgD;AAChD,eAAO,MAAM,0BAA0B,QAAwB,CAAC;AAEhE,yDAAyD;AACzD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEvD;AAED,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,qBAOa,iBAAkB,SAAQ,UAAU;IAC/C,6EAA6E;IAE7E,SAAS,EAAE,MAAM,CAAM;IAEvB,uCAAuC;IAEvC,SAAS,EAAE,MAAM,CAAM;IAEvB;;;;OAIG;IAEH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,wEAAwE;IAExE,MAAM,EAAE,MAAM,CAAM;IAEpB;;;;;;;;OAQG;IAEH,eAAe,EAAE,MAAM,CAAK;IAE5B;;;;OAIG;IAEH,QAAQ,EAAE,MAAM,CAAK;IAErB;;;;OAIG;IAEH,iBAAiB,EAAE,MAAM,CAAK;IAE9B,mEAAmE;IAEnE,eAAe,EAAE,MAAM,CAAQ;IAE/B,6EAA6E;IAE7E,uBAAuB,EAAE,OAAO,CAAS;IAEzC;;;OAGG;IAEH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAErC,yEAAyE;IAEzE,wBAAwB,EAAE,OAAO,CAAS;gBAE9B,OAAO,GAAE,wBAA6B;IA2BlD,0EAA0E;IAC1E,kBAAkB,IAAI,MAAM,EAAE;IAW9B;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAcrC;;;;;;;;;OASG;IACH,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAwB3C;;;;;;;OAOG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAiBxC;;;;;OAKG;IACH,cAAc,IAAI,OAAO;IAIV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtC,OAAO,CAAC,yBAAyB;IAoCjC;;;;;;;;;;;;;OAaG;YACW,8BAA8B;IAgC5C,4EAA4E;YAC9D,oBAAoB;CAYnC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Durable daily receipt for one member's contribution to one field.
|
|
4
|
+
*
|
|
5
|
+
* The counter action creates this before incrementing its aggregate. Its
|
|
6
|
+
* natural key makes the anti-inflation rule durable across requests and
|
|
7
|
+
* replicas: one `(tenant, user, object, field, UTC day)` sample may affect
|
|
8
|
+
* usage evidence. Receipts intentionally retain no submitted value.
|
|
9
|
+
*/
|
|
10
|
+
export declare class FieldUsageReportReceipt extends SmrtObject {
|
|
11
|
+
tenantId?: string;
|
|
12
|
+
userId: string;
|
|
13
|
+
objectRef: string;
|
|
14
|
+
fieldName: string;
|
|
15
|
+
period: string;
|
|
16
|
+
constructor(options?: FieldUsageReportReceiptOptions);
|
|
17
|
+
}
|
|
18
|
+
export interface FieldUsageReportReceiptOptions extends SmrtObjectOptions {
|
|
19
|
+
tenantId?: string;
|
|
20
|
+
userId?: string;
|
|
21
|
+
objectRef?: string;
|
|
22
|
+
fieldName?: string;
|
|
23
|
+
period?: string;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=FieldUsageReportReceipt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldUsageReportReceipt.d.ts","sourceRoot":"","sources":["../../src/models/FieldUsageReportReceipt.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAGlC;;;;;;;GAOG;AACH,qBAaa,uBAAwB,SAAQ,UAAU;IAErD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,MAAM,EAAE,MAAM,CAAM;IAGpB,SAAS,EAAE,MAAM,CAAM;IAGvB,SAAS,EAAE,MAAM,CAAM;IAGvB,MAAM,EAAE,MAAM,CAAM;gBAER,OAAO,GAAE,8BAAmC;CAQzD;AAED,MAAM,WAAW,8BAA+B,SAAQ,iBAAiB;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|