@ai-sdlc/orchestrator 0.10.0 → 0.14.0
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/adapters.d.ts +42 -3
- package/dist/adapters.js +133 -3
- package/dist/admission-composite.d.ts +112 -1
- package/dist/admission-composite.js +85 -4
- package/dist/admission-enrichment.d.ts +12 -3
- package/dist/admission-enrichment.js +64 -13
- package/dist/admission-score.d.ts +30 -0
- package/dist/admission-score.js +4 -1
- package/dist/analysis/file-walker.js +5 -0
- package/dist/backlog-adapter.d.ts +45 -0
- package/dist/backlog-adapter.js +145 -1
- package/dist/calibration.d.ts +81 -0
- package/dist/calibration.js +76 -0
- package/dist/cli/commands/git-remote.js +1 -1
- package/dist/cli/commands/init-features.d.ts +301 -2
- package/dist/cli/commands/init-features.js +647 -7
- package/dist/cli/commands/init-templates.d.ts +198 -1
- package/dist/cli/commands/init-templates.js +943 -1
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.js +147 -5
- package/dist/cli/commands/run.js +9 -1
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +42 -8
- package/dist/compliance/composer.d.ts +79 -0
- package/dist/compliance/composer.js +258 -0
- package/dist/compliance/errors.d.ts +64 -0
- package/dist/compliance/errors.js +85 -0
- package/dist/compliance/loader.d.ts +52 -0
- package/dist/compliance/loader.js +124 -0
- package/dist/compliance/types.d.ts +184 -0
- package/dist/compliance/types.js +41 -0
- package/dist/compliance-clearance.d.ts +269 -0
- package/dist/compliance-clearance.js +269 -0
- package/dist/config.js +17 -0
- package/dist/cost-tracker.d.ts +22 -0
- package/dist/cost-tracker.js +41 -0
- package/dist/cycle-utils.js +14 -1
- package/dist/database/adapters/external.js +5 -1
- package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
- package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
- package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
- package/dist/embedding/consumers/tessellation-drift.js +76 -0
- package/dist/embedding/cross-provider.d.ts +78 -0
- package/dist/embedding/cross-provider.js +75 -0
- package/dist/embedding/deprecation.d.ts +151 -0
- package/dist/embedding/deprecation.js +229 -0
- package/dist/embedding/errors.d.ts +90 -0
- package/dist/embedding/errors.js +150 -0
- package/dist/embedding/index.d.ts +29 -0
- package/dist/embedding/index.js +24 -0
- package/dist/embedding/pipeline-load.d.ts +146 -0
- package/dist/embedding/pipeline-load.js +178 -0
- package/dist/embedding/registry.d.ts +45 -0
- package/dist/embedding/registry.js +61 -0
- package/dist/embedding/stale-vector.d.ts +110 -0
- package/dist/embedding/stale-vector.js +92 -0
- package/dist/embedding/storage/index.d.ts +51 -0
- package/dist/embedding/storage/index.js +43 -0
- package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
- package/dist/embedding/storage/jsonl-backend.js +332 -0
- package/dist/embedding/storage/types.d.ts +135 -0
- package/dist/embedding/storage/types.js +13 -0
- package/dist/embedding/types.d.ts +180 -0
- package/dist/embedding/types.js +10 -0
- package/dist/execute.d.ts +29 -2
- package/dist/execute.js +200 -38
- package/dist/fix-review.js +1 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.js +19 -2
- package/dist/journey/inheritance-validator.d.ts +396 -0
- package/dist/journey/inheritance-validator.js +370 -0
- package/dist/journey/metric-snapshot.d.ts +403 -0
- package/dist/journey/metric-snapshot.js +370 -0
- package/dist/journey/state-id-drift-rule.d.ts +137 -0
- package/dist/journey/state-id-drift-rule.js +245 -0
- package/dist/journey-sa2-router.d.ts +395 -0
- package/dist/journey-sa2-router.js +308 -0
- package/dist/otel-exporter.js +0 -3
- package/dist/runners/claude-code.js +3 -3
- package/dist/runners/review-agent.js +6 -2
- package/dist/runners/runner-registry.d.ts +36 -0
- package/dist/runners/runner-registry.js +90 -0
- package/dist/runtime/attestations.d.ts +173 -13
- package/dist/runtime/attestations.js +265 -43
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/sa-scoring/exemplar-bank.js +3 -1
- package/dist/sa-scoring/layer3-llm.js +6 -1
- package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
- package/dist/sa-scoring/revision-proposal-config.js +198 -0
- package/dist/sa-scoring/revision-proposal.d.ts +285 -0
- package/dist/sa-scoring/revision-proposal.js +417 -0
- package/dist/shared.d.ts +28 -0
- package/dist/shared.js +37 -0
- package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
- package/dist/signal-ingestion/adapters/community-thread.js +55 -0
- package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
- package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
- package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
- package/dist/signal-ingestion/adapters/manual.js +112 -0
- package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
- package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
- package/dist/signal-ingestion/classifier.d.ts +205 -0
- package/dist/signal-ingestion/classifier.js +494 -0
- package/dist/signal-ingestion/clustering-types.d.ts +36 -0
- package/dist/signal-ingestion/clustering-types.js +14 -0
- package/dist/signal-ingestion/clustering.d.ts +200 -0
- package/dist/signal-ingestion/clustering.js +413 -0
- package/dist/signal-ingestion/config.d.ts +351 -0
- package/dist/signal-ingestion/config.js +587 -0
- package/dist/signal-ingestion/d1.d.ts +252 -0
- package/dist/signal-ingestion/d1.js +235 -0
- package/dist/signal-ingestion/errors.d.ts +73 -0
- package/dist/signal-ingestion/errors.js +108 -0
- package/dist/signal-ingestion/governance-events.d.ts +181 -0
- package/dist/signal-ingestion/governance-events.js +189 -0
- package/dist/signal-ingestion/index.d.ts +35 -0
- package/dist/signal-ingestion/index.js +53 -0
- package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
- package/dist/signal-ingestion/manual-share-metric.js +106 -0
- package/dist/signal-ingestion/registry.d.ts +40 -0
- package/dist/signal-ingestion/registry.js +137 -0
- package/dist/signal-ingestion/residency.d.ts +227 -0
- package/dist/signal-ingestion/residency.js +238 -0
- package/dist/signal-ingestion/significance.d.ts +554 -0
- package/dist/signal-ingestion/significance.js +555 -0
- package/dist/signal-ingestion/types.d.ts +191 -0
- package/dist/signal-ingestion/types.js +8 -0
- package/dist/substrate/drift-composition.d.ts +270 -0
- package/dist/substrate/drift-composition.js +306 -0
- package/dist/substrate/drift-tui-surface.d.ts +61 -0
- package/dist/substrate/drift-tui-surface.js +102 -0
- package/dist/substrate/identity-class.d.ts +176 -0
- package/dist/substrate/identity-class.js +201 -0
- package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
- package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
- package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
- package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
- package/dist/tessellation/rule-registry.d.ts +269 -0
- package/dist/tessellation/rule-registry.js +92 -0
- package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
- package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
- package/dist/tessellation-admission.d.ts +162 -0
- package/dist/tessellation-admission.js +146 -0
- package/dist/tessellation-drift.d.ts +246 -0
- package/dist/tessellation-drift.js +250 -0
- package/dist/validate-config.js +13 -0
- package/dist/validate-issue.js +2 -2
- package/dist/variant/cardinality-activation.d.ts +126 -0
- package/dist/variant/cardinality-activation.js +101 -0
- package/dist/variant/deprecation-lifecycle.d.ts +184 -0
- package/dist/variant/deprecation-lifecycle.js +208 -0
- package/dist/variant/drift-extension.d.ts +136 -0
- package/dist/variant/drift-extension.js +164 -0
- package/dist/variant/engineering-review.d.ts +185 -0
- package/dist/variant/engineering-review.js +142 -0
- package/dist/variant/index.d.ts +32 -0
- package/dist/variant/index.js +32 -0
- package/dist/variant/inheritance-validator.d.ts +165 -0
- package/dist/variant/inheritance-validator.js +139 -0
- package/dist/variant/internal-adopter/index.d.ts +11 -0
- package/dist/variant/internal-adopter/index.js +10 -0
- package/dist/variant/internal-adopter/products.d.ts +156 -0
- package/dist/variant/internal-adopter/products.js +366 -0
- package/dist/variant-admission.d.ts +316 -0
- package/dist/variant-admission.js +247 -0
- package/dist/webhook-manager.js +7 -1
- package/package.json +15 -12
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AdapterCredentialNotConfigured, AdapterCredentialRejected } from '../errors.js';
|
|
2
|
+
export const DEFAULT_IN_APP_FEEDBACK_ENV_VAR = 'SIGNAL_IN_APP_FEEDBACK_API_KEY';
|
|
3
|
+
export class InAppFeedbackSignalSourceAdapter {
|
|
4
|
+
options;
|
|
5
|
+
name = 'signal-source-in-app-feedback';
|
|
6
|
+
defaultTier = 1;
|
|
7
|
+
/** OAuth NOT required — env-var-based API key only. */
|
|
8
|
+
requiresOAuth = false;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
/** Resolve the env var name for this adapter. */
|
|
13
|
+
get envVarName() {
|
|
14
|
+
return this.options.envVarName ?? DEFAULT_IN_APP_FEEDBACK_ENV_VAR;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Adapter availability: env-var-probe-based. Returns `true` when the
|
|
18
|
+
* configured env var is present and non-empty, OR when the `available`
|
|
19
|
+
* test override is set. Returns `false` otherwise — the registry will
|
|
20
|
+
* surface `Decision: adapter-credential-not-configured` rather than
|
|
21
|
+
* blocking the pipeline.
|
|
22
|
+
*/
|
|
23
|
+
async isAvailable() {
|
|
24
|
+
if (this.options.available !== undefined)
|
|
25
|
+
return this.options.available;
|
|
26
|
+
const env = this.options.env ?? process.env;
|
|
27
|
+
const v = env[this.envVarName];
|
|
28
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
29
|
+
}
|
|
30
|
+
async fetchSignals(since) {
|
|
31
|
+
// Test overrides take precedence so fixtures can exercise both failure
|
|
32
|
+
// paths regardless of env state.
|
|
33
|
+
if (this.options.credentialNotConfigured) {
|
|
34
|
+
throw new AdapterCredentialNotConfigured(this.name, this.envVarName);
|
|
35
|
+
}
|
|
36
|
+
if (this.options.credentialRejected) {
|
|
37
|
+
throw new AdapterCredentialRejected(this.name);
|
|
38
|
+
}
|
|
39
|
+
// Production path: re-probe env var (unless `available: true` was set as
|
|
40
|
+
// an explicit bypass for hermetic tests that don't care about env probing).
|
|
41
|
+
if (this.options.available === undefined) {
|
|
42
|
+
const env = this.options.env ?? process.env;
|
|
43
|
+
const v = env[this.envVarName];
|
|
44
|
+
if (!(typeof v === 'string' && v.trim().length > 0)) {
|
|
45
|
+
throw new AdapterCredentialNotConfigured(this.name, this.envVarName);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return (this.options.signals ?? []).filter((signal) => signal.sourceTimestamp >= since);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=in-app-feedback.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { RawSignal, SignalSourceAdapter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* RFC-0030 OQ-13.4 v0.3 re-walkthrough default cap. 10 manual signals per
|
|
4
|
+
* operator per UTC day. Reasoning: a single operator processing >10
|
|
5
|
+
* high-signal conversations per day for the framework specifically is
|
|
6
|
+
* exceptional; 10/day allows ~50/week (sales-team conference-week scenarios);
|
|
7
|
+
* per-org override via `manualEntry.dailyCapPerOperator` for genuinely
|
|
8
|
+
* high-throughput contexts. Anti-gaming guardrail: makes sustained gaming
|
|
9
|
+
* require sustained effort while not blocking legitimate use.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_MANUAL_DAILY_CAP_PER_OPERATOR = 10;
|
|
12
|
+
export type ManualSignalInput = Omit<RawSignal, 'attestedAt'> & {
|
|
13
|
+
attestedAt?: Date;
|
|
14
|
+
};
|
|
15
|
+
export interface ManualSignalSourceAdapterOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Per-operator daily cap (UTC day buckets). When exceeded, `addSignal`
|
|
18
|
+
* raises `ManualSignalRateLimitExceeded` so the registry can surface
|
|
19
|
+
* `Decision: manual-signal-rate-limit-exceeded`. Defaults to
|
|
20
|
+
* `DEFAULT_MANUAL_DAILY_CAP_PER_OPERATOR` (10). Setting to `0` or a negative
|
|
21
|
+
* value DISABLES rate limiting (operator opt-out).
|
|
22
|
+
*/
|
|
23
|
+
dailyCapPerOperator?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Seed entries pre-populating the adapter. Useful for tests; production
|
|
26
|
+
* adapters typically construct empty then `addSignal()` per CLI submit.
|
|
27
|
+
*/
|
|
28
|
+
initialSignals?: ManualSignalInput[];
|
|
29
|
+
}
|
|
30
|
+
export declare class ManualSignalSourceAdapter implements SignalSourceAdapter {
|
|
31
|
+
readonly name = "signal-source-manual";
|
|
32
|
+
readonly defaultTier = 1;
|
|
33
|
+
/** Manual entry never requires OAuth (no upstream service). */
|
|
34
|
+
readonly requiresOAuth = false;
|
|
35
|
+
private readonly signals;
|
|
36
|
+
private readonly dailyCapPerOperator;
|
|
37
|
+
/**
|
|
38
|
+
* Per-operator UTC-day bucket counter:
|
|
39
|
+
* key = `${attestedBy}|${YYYY-MM-DD}` (UTC date)
|
|
40
|
+
* val = count of manual signals accepted in that bucket
|
|
41
|
+
*
|
|
42
|
+
* Rebuilt lazily — addSignal increments + reads from this map; the bucket
|
|
43
|
+
* key derivation uses the signal's `attestedAt` (auto-filled to `now` when
|
|
44
|
+
* omitted, so the count always lands in the correct UTC day).
|
|
45
|
+
*/
|
|
46
|
+
private readonly perOperatorPerDayCount;
|
|
47
|
+
constructor(initialSignalsOrOptions?: ManualSignalInput[] | ManualSignalSourceAdapterOptions);
|
|
48
|
+
/** Effective per-operator daily cap (post-defaulting). Test helper. */
|
|
49
|
+
get effectiveDailyCap(): number;
|
|
50
|
+
isAvailable(): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Add a manual signal with attestation + rate-limit enforcement.
|
|
53
|
+
*
|
|
54
|
+
* Order of validation:
|
|
55
|
+
* 1. `attestedBy` required → `ManualSignalIncomplete` (preserved from v0.2).
|
|
56
|
+
* 2. `attestedAt` defaulted to `now`.
|
|
57
|
+
* 3. Per-operator UTC-day cap → `ManualSignalRateLimitExceeded` when
|
|
58
|
+
* `count(attestedBy, utcDate(attestedAt)) >= dailyCapPerOperator`.
|
|
59
|
+
* (Skipped when `dailyCapPerOperator <= 0`.)
|
|
60
|
+
*
|
|
61
|
+
* Returns the persisted RawSignal (with `attestedAt` filled + `evidenceUrl`
|
|
62
|
+
* preserved verbatim) when accepted.
|
|
63
|
+
*/
|
|
64
|
+
addSignal(input: ManualSignalInput, now?: Date): RawSignal;
|
|
65
|
+
fetchSignals(since: Date): Promise<RawSignal[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Test helper: current per-operator UTC-day count for the given attestedBy.
|
|
68
|
+
* Exposed so unit tests can assert rate-limit bookkeeping without reaching
|
|
69
|
+
* into the private map.
|
|
70
|
+
*/
|
|
71
|
+
countForOperatorOnDate(attestedBy: string, utcDateIso: string): number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* UTC date key in `YYYY-MM-DD` form. Hoisted so the manual-share metric
|
|
75
|
+
* helper (see `manual-share-metric.ts`) can use the same bucketing rule.
|
|
76
|
+
*/
|
|
77
|
+
export declare function utcDateKey(d: Date): string;
|
|
78
|
+
//# sourceMappingURL=manual.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ManualSignalIncomplete, ManualSignalRateLimitExceeded } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* RFC-0030 OQ-13.4 v0.3 re-walkthrough default cap. 10 manual signals per
|
|
4
|
+
* operator per UTC day. Reasoning: a single operator processing >10
|
|
5
|
+
* high-signal conversations per day for the framework specifically is
|
|
6
|
+
* exceptional; 10/day allows ~50/week (sales-team conference-week scenarios);
|
|
7
|
+
* per-org override via `manualEntry.dailyCapPerOperator` for genuinely
|
|
8
|
+
* high-throughput contexts. Anti-gaming guardrail: makes sustained gaming
|
|
9
|
+
* require sustained effort while not blocking legitimate use.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_MANUAL_DAILY_CAP_PER_OPERATOR = 10;
|
|
12
|
+
export class ManualSignalSourceAdapter {
|
|
13
|
+
name = 'signal-source-manual';
|
|
14
|
+
defaultTier = 1;
|
|
15
|
+
/** Manual entry never requires OAuth (no upstream service). */
|
|
16
|
+
requiresOAuth = false;
|
|
17
|
+
signals = [];
|
|
18
|
+
dailyCapPerOperator;
|
|
19
|
+
/**
|
|
20
|
+
* Per-operator UTC-day bucket counter:
|
|
21
|
+
* key = `${attestedBy}|${YYYY-MM-DD}` (UTC date)
|
|
22
|
+
* val = count of manual signals accepted in that bucket
|
|
23
|
+
*
|
|
24
|
+
* Rebuilt lazily — addSignal increments + reads from this map; the bucket
|
|
25
|
+
* key derivation uses the signal's `attestedAt` (auto-filled to `now` when
|
|
26
|
+
* omitted, so the count always lands in the correct UTC day).
|
|
27
|
+
*/
|
|
28
|
+
perOperatorPerDayCount = new Map();
|
|
29
|
+
constructor(initialSignalsOrOptions = []) {
|
|
30
|
+
let initial;
|
|
31
|
+
if (Array.isArray(initialSignalsOrOptions)) {
|
|
32
|
+
// Legacy ctor signature: `new ManualSignalSourceAdapter([...])`
|
|
33
|
+
initial = initialSignalsOrOptions;
|
|
34
|
+
this.dailyCapPerOperator = DEFAULT_MANUAL_DAILY_CAP_PER_OPERATOR;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
initial = initialSignalsOrOptions.initialSignals ?? [];
|
|
38
|
+
this.dailyCapPerOperator =
|
|
39
|
+
initialSignalsOrOptions.dailyCapPerOperator ?? DEFAULT_MANUAL_DAILY_CAP_PER_OPERATOR;
|
|
40
|
+
}
|
|
41
|
+
for (const signal of initial)
|
|
42
|
+
this.addSignal(signal);
|
|
43
|
+
}
|
|
44
|
+
/** Effective per-operator daily cap (post-defaulting). Test helper. */
|
|
45
|
+
get effectiveDailyCap() {
|
|
46
|
+
return this.dailyCapPerOperator;
|
|
47
|
+
}
|
|
48
|
+
async isAvailable() {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Add a manual signal with attestation + rate-limit enforcement.
|
|
53
|
+
*
|
|
54
|
+
* Order of validation:
|
|
55
|
+
* 1. `attestedBy` required → `ManualSignalIncomplete` (preserved from v0.2).
|
|
56
|
+
* 2. `attestedAt` defaulted to `now`.
|
|
57
|
+
* 3. Per-operator UTC-day cap → `ManualSignalRateLimitExceeded` when
|
|
58
|
+
* `count(attestedBy, utcDate(attestedAt)) >= dailyCapPerOperator`.
|
|
59
|
+
* (Skipped when `dailyCapPerOperator <= 0`.)
|
|
60
|
+
*
|
|
61
|
+
* Returns the persisted RawSignal (with `attestedAt` filled + `evidenceUrl`
|
|
62
|
+
* preserved verbatim) when accepted.
|
|
63
|
+
*/
|
|
64
|
+
addSignal(input, now = new Date()) {
|
|
65
|
+
if (!input.attestedBy) {
|
|
66
|
+
throw new ManualSignalIncomplete(input.sourceId);
|
|
67
|
+
}
|
|
68
|
+
const attestedAt = input.attestedAt ?? now;
|
|
69
|
+
// Rate-limit bucket keys MUST use wall-clock `now`, not caller-supplied
|
|
70
|
+
// `attestedAt`. A spoofed attestedAt (e.g. `new Date('2099-01-01')`)
|
|
71
|
+
// would otherwise create a fresh per-day bucket on each submit and
|
|
72
|
+
// bypass the daily cap entirely. The persisted attestedAt remains
|
|
73
|
+
// caller-supplied for audit fidelity (OQ-13.4 v0.3 anti-gaming fix).
|
|
74
|
+
const utcDate = utcDateKey(now);
|
|
75
|
+
const bucketKey = `${input.attestedBy}|${utcDate}`;
|
|
76
|
+
if (this.dailyCapPerOperator > 0) {
|
|
77
|
+
const currentCount = this.perOperatorPerDayCount.get(bucketKey) ?? 0;
|
|
78
|
+
if (currentCount >= this.dailyCapPerOperator) {
|
|
79
|
+
throw new ManualSignalRateLimitExceeded(input.attestedBy, this.dailyCapPerOperator, utcDate, input.sourceId);
|
|
80
|
+
}
|
|
81
|
+
this.perOperatorPerDayCount.set(bucketKey, currentCount + 1);
|
|
82
|
+
}
|
|
83
|
+
const signal = {
|
|
84
|
+
...input,
|
|
85
|
+
attestedAt,
|
|
86
|
+
};
|
|
87
|
+
this.signals.push(signal);
|
|
88
|
+
return signal;
|
|
89
|
+
}
|
|
90
|
+
async fetchSignals(since) {
|
|
91
|
+
return this.signals.filter((signal) => signal.sourceTimestamp >= since);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Test helper: current per-operator UTC-day count for the given attestedBy.
|
|
95
|
+
* Exposed so unit tests can assert rate-limit bookkeeping without reaching
|
|
96
|
+
* into the private map.
|
|
97
|
+
*/
|
|
98
|
+
countForOperatorOnDate(attestedBy, utcDateIso) {
|
|
99
|
+
return this.perOperatorPerDayCount.get(`${attestedBy}|${utcDateIso}`) ?? 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* UTC date key in `YYYY-MM-DD` form. Hoisted so the manual-share metric
|
|
104
|
+
* helper (see `manual-share-metric.ts`) can use the same bucketing rule.
|
|
105
|
+
*/
|
|
106
|
+
export function utcDateKey(d) {
|
|
107
|
+
const y = d.getUTCFullYear();
|
|
108
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
109
|
+
const day = String(d.getUTCDate()).padStart(2, '0');
|
|
110
|
+
return `${y}-${m}-${day}`;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=manual.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RawSignal, SignalSourceAdapter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default env var holding the Zendesk personal access token (PAT) per
|
|
4
|
+
* RFC-0030 OQ-13.1 v0.3 re-walkthrough. Env-var-based credential model;
|
|
5
|
+
* OAuth-scoped Zendesk integration deferred to the credential-management RFC.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_SUPPORT_TICKET_ENV_VAR = "SIGNAL_ZENDESK_PAT";
|
|
8
|
+
export interface SupportTicketAdapterOptions {
|
|
9
|
+
signals?: RawSignal[];
|
|
10
|
+
available?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Legacy test override (pre-OQ-13.1 v0.3): emits the collapsed
|
|
13
|
+
* `adapter-credential-invalid` Decision. Preserved for backward-compat.
|
|
14
|
+
* New tests should use `credentialNotConfigured` or `credentialRejected`.
|
|
15
|
+
*/
|
|
16
|
+
credentialInvalid?: boolean;
|
|
17
|
+
/** RFC-0030 OQ-13.1 v0.3: env-var missing test override. */
|
|
18
|
+
credentialNotConfigured?: boolean;
|
|
19
|
+
/** RFC-0030 OQ-13.1 v0.3: env-var present but upstream rejected test override. */
|
|
20
|
+
credentialRejected?: boolean;
|
|
21
|
+
/** Override the env var name (defaults to `SIGNAL_ZENDESK_PAT`). */
|
|
22
|
+
envVarName?: string;
|
|
23
|
+
/** Optional explicit env accessor (test seam). Defaults to `process.env`. */
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
/**
|
|
26
|
+
* When `true`, the adapter probes its env var as part of availability /
|
|
27
|
+
* fetchSignals. Defaults to `false` so the legacy in-memory test usage
|
|
28
|
+
* (passing `signals: [...]` without env-var setup) continues to work.
|
|
29
|
+
*
|
|
30
|
+
* v1-shipping deployments should set `probeEnvVar: true` to opt in to the
|
|
31
|
+
* OQ-13.1 v0.3 dual-Decision routing.
|
|
32
|
+
*/
|
|
33
|
+
probeEnvVar?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare class SupportTicketSignalSourceAdapter implements SignalSourceAdapter {
|
|
36
|
+
private readonly options;
|
|
37
|
+
readonly name = "signal-source-support-ticket";
|
|
38
|
+
readonly defaultTier = 1;
|
|
39
|
+
/** OAuth NOT required for the v1 Zendesk PAT credential model. */
|
|
40
|
+
readonly requiresOAuth = false;
|
|
41
|
+
constructor(options?: SupportTicketAdapterOptions);
|
|
42
|
+
/** Resolve the env var name for this adapter. */
|
|
43
|
+
get envVarName(): string;
|
|
44
|
+
isAvailable(): Promise<boolean>;
|
|
45
|
+
fetchSignals(since: Date): Promise<RawSignal[]>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=support-ticket.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AdapterCredentialInvalid, AdapterCredentialNotConfigured, AdapterCredentialRejected, } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default env var holding the Zendesk personal access token (PAT) per
|
|
4
|
+
* RFC-0030 OQ-13.1 v0.3 re-walkthrough. Env-var-based credential model;
|
|
5
|
+
* OAuth-scoped Zendesk integration deferred to the credential-management RFC.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_SUPPORT_TICKET_ENV_VAR = 'SIGNAL_ZENDESK_PAT';
|
|
8
|
+
export class SupportTicketSignalSourceAdapter {
|
|
9
|
+
options;
|
|
10
|
+
name = 'signal-source-support-ticket';
|
|
11
|
+
defaultTier = 1;
|
|
12
|
+
/** OAuth NOT required for the v1 Zendesk PAT credential model. */
|
|
13
|
+
requiresOAuth = false;
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
/** Resolve the env var name for this adapter. */
|
|
18
|
+
get envVarName() {
|
|
19
|
+
return this.options.envVarName ?? DEFAULT_SUPPORT_TICKET_ENV_VAR;
|
|
20
|
+
}
|
|
21
|
+
async isAvailable() {
|
|
22
|
+
if (this.options.available !== undefined)
|
|
23
|
+
return this.options.available;
|
|
24
|
+
if (this.options.probeEnvVar) {
|
|
25
|
+
const env = this.options.env ?? process.env;
|
|
26
|
+
const v = env[this.envVarName];
|
|
27
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
async fetchSignals(since) {
|
|
32
|
+
if (this.options.credentialNotConfigured) {
|
|
33
|
+
throw new AdapterCredentialNotConfigured(this.name, this.envVarName);
|
|
34
|
+
}
|
|
35
|
+
if (this.options.credentialRejected) {
|
|
36
|
+
throw new AdapterCredentialRejected(this.name);
|
|
37
|
+
}
|
|
38
|
+
if (this.options.credentialInvalid) {
|
|
39
|
+
throw new AdapterCredentialInvalid(this.name);
|
|
40
|
+
}
|
|
41
|
+
if (this.options.probeEnvVar && this.options.available === undefined) {
|
|
42
|
+
const env = this.options.env ?? process.env;
|
|
43
|
+
const v = env[this.envVarName];
|
|
44
|
+
if (!(typeof v === 'string' && v.trim().length > 0)) {
|
|
45
|
+
throw new AdapterCredentialNotConfigured(this.name, this.envVarName);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return (this.options.signals ?? []).filter((signal) => signal.sourceTimestamp >= since);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=support-ticket.js.map
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0030 Phase 2 — Signal classifier.
|
|
3
|
+
*
|
|
4
|
+
* Classifies raw signals on three deterministic axes and applies the
|
|
5
|
+
* English-only language gate (OQ-13.2 resolution):
|
|
6
|
+
*
|
|
7
|
+
* 1. **Tier** — enterprise / mid / smb / free / churned (metadata-driven).
|
|
8
|
+
* Inference order: adapter-provided `customerTier` → customerId lookup →
|
|
9
|
+
* adapter's `defaultTier` (mapped to smb for Tier 1, free for Tier 2).
|
|
10
|
+
*
|
|
11
|
+
* 2. **ICP resonance** — strong / partial / weak (BM25 default; embedding
|
|
12
|
+
* when RFC-0019 adapter is configured — currently always BM25 in v1).
|
|
13
|
+
* For v1 the classifier receives an explicit `icpSegments` list from the
|
|
14
|
+
* caller. BM25 matching measures how closely the signal payload matches
|
|
15
|
+
* the declared ICP segments.
|
|
16
|
+
*
|
|
17
|
+
* 3. **Recency decay** — exponential decay: exp(-age_days × ln(2) / half_life).
|
|
18
|
+
* Applied at scoring time so the pipeline doesn't need to re-compute as
|
|
19
|
+
* time passes (age is passed as input). Half-life is read from config
|
|
20
|
+
* (default 30 days).
|
|
21
|
+
*
|
|
22
|
+
* **Language gate** (OQ-13.2 v0.3 re-walkthrough): non-accepted-language signal
|
|
23
|
+
* payloads are detected via the `franc` library (deterministic; <10ms per
|
|
24
|
+
* signal; JS-native; MIT-licensed; 95%+ accuracy on text >50 chars; returns
|
|
25
|
+
* ISO 639-3 codes). Signals in unsupported languages are dropped and logged as
|
|
26
|
+
* `Decision: signal-language-unsupported`. Per-org `acceptedLanguages` config
|
|
27
|
+
* is respected (default `['en']`); multi-language opt-in (e.g. `['en', 'fr',
|
|
28
|
+
* 'es']`) accepts those languages knowingly — adopters take on documented BM25
|
|
29
|
+
* quality degradation (~15-30% precision drop without per-language stopwords)
|
|
30
|
+
* in exchange for non-English signal coverage.
|
|
31
|
+
*
|
|
32
|
+
* All multipliers and weights are read from `SignalIngestionConfig`; they are
|
|
33
|
+
* NOT hardcoded, satisfying AC #6.
|
|
34
|
+
*
|
|
35
|
+
* @module signal-ingestion/classifier
|
|
36
|
+
*/
|
|
37
|
+
import type { CustomerTier, RawSignal, SignalTier } from './types.js';
|
|
38
|
+
import type { SignalIngestionConfig, TierMultipliers, IcpResonanceWeights } from './config.js';
|
|
39
|
+
export type ICPResonance = 'strong' | 'partial' | 'weak';
|
|
40
|
+
/** The result of classifying a single raw signal on all three axes. */
|
|
41
|
+
export interface ClassifiedSignal {
|
|
42
|
+
/** Original signal, unmodified. */
|
|
43
|
+
signal: RawSignal;
|
|
44
|
+
/** Resolved customer tier (enterprise / mid / smb / free / churned). */
|
|
45
|
+
customerTier: CustomerTier;
|
|
46
|
+
/** ICP resonance level. */
|
|
47
|
+
icpResonance: ICPResonance;
|
|
48
|
+
/**
|
|
49
|
+
* Recency decay factor in [0, 1].
|
|
50
|
+
* 1.0 = fresh signal; approaches 0 for very old signals.
|
|
51
|
+
* Computed at classification time using the provided `asOf` date.
|
|
52
|
+
*/
|
|
53
|
+
recencyDecay: number;
|
|
54
|
+
/**
|
|
55
|
+
* Tier multiplier looked up from config for this signal's customerTier.
|
|
56
|
+
* Convenience field — callers do NOT need to re-read the config.
|
|
57
|
+
*/
|
|
58
|
+
tierMultiplier: number;
|
|
59
|
+
/**
|
|
60
|
+
* ICP resonance weight looked up from config.
|
|
61
|
+
* Convenience field.
|
|
62
|
+
*/
|
|
63
|
+
icpResonanceWeight: number;
|
|
64
|
+
/**
|
|
65
|
+
* Signal-level base weight.
|
|
66
|
+
* - Tier 1 sources: 1.0
|
|
67
|
+
* - Tier 2 sources: 0.3 in Phase 2 (significance-threshold evaluation
|
|
68
|
+
* is Phase 4's job; Phase 4 will downgrade to 0.0 for Tier 2 sources
|
|
69
|
+
* below threshold, excluding them from D1 scoring).
|
|
70
|
+
* Phase 4/5 consumers MUST NOT rely on `baseWeight === 0` to detect
|
|
71
|
+
* exclusions until Phase 4 ships — Phase 2 never emits 0.0.
|
|
72
|
+
*/
|
|
73
|
+
baseWeight: number;
|
|
74
|
+
}
|
|
75
|
+
/** Emitted when a signal is dropped for language reasons (RFC-0030 OQ-13.2 v0.3). */
|
|
76
|
+
export interface SignalLanguageUnsupportedDecision {
|
|
77
|
+
type: 'Decision';
|
|
78
|
+
decision: 'signal-language-unsupported';
|
|
79
|
+
sourceId: string;
|
|
80
|
+
/**
|
|
81
|
+
* Detected language as ISO 639-3 three-letter code (e.g. `'cmn'`, `'fra'`,
|
|
82
|
+
* `'spa'`, `'eng'`). `'und'` when text was too short or no script matched.
|
|
83
|
+
* For backwards compatibility with v0.2 dashboards reading `detectedScript`,
|
|
84
|
+
* a coarse script family hint (`'cjk'`, `'cyrillic'`, `'arabic'`, `'latin'`)
|
|
85
|
+
* may still be derivable from the language code via the franc data tables.
|
|
86
|
+
*/
|
|
87
|
+
detectedLanguage: string;
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated since v0.3 — use `detectedLanguage`. Retained for one release
|
|
90
|
+
* window so downstream consumers (TUI, runbook examples) can migrate.
|
|
91
|
+
* Populated with a coarse script-family hint derived from `detectedLanguage`
|
|
92
|
+
* (`'cjk'`, `'cyrillic'`, `'arabic'`, `'hebrew'`, `'devanagari'`, `'thai'`,
|
|
93
|
+
* `'greek'`, `'armenian'`, `'georgian'`, `'latin'`, or `'unknown'`).
|
|
94
|
+
*/
|
|
95
|
+
detectedScript: string;
|
|
96
|
+
acceptedLanguages: string[];
|
|
97
|
+
message: string;
|
|
98
|
+
}
|
|
99
|
+
/** Result of classifying a batch of signals. */
|
|
100
|
+
export interface ClassificationResult {
|
|
101
|
+
/** Successfully classified signals. */
|
|
102
|
+
classified: ClassifiedSignal[];
|
|
103
|
+
/**
|
|
104
|
+
* Signals dropped for language reasons, emitted as Decision records per
|
|
105
|
+
* RFC-0030 OQ-13.2 resolution.
|
|
106
|
+
*/
|
|
107
|
+
languageDecisions: SignalLanguageUnsupportedDecision[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Optional customer-tier registry for resolving tier from `customerId`.
|
|
111
|
+
* Adopters can provide their own implementation; the pipeline falls back
|
|
112
|
+
* to adapter defaults when no registry is configured or when the
|
|
113
|
+
* `customerId` is not found.
|
|
114
|
+
*/
|
|
115
|
+
export interface CustomerTierRegistry {
|
|
116
|
+
resolve(customerId: string): CustomerTier | undefined;
|
|
117
|
+
}
|
|
118
|
+
export interface ClassifySignalsOptions {
|
|
119
|
+
/**
|
|
120
|
+
* Point in time used to compute signal ages.
|
|
121
|
+
* Defaults to `new Date()` when not provided.
|
|
122
|
+
*/
|
|
123
|
+
asOf?: Date;
|
|
124
|
+
/**
|
|
125
|
+
* Resolved signal ingestion configuration. Defaults to the built-in
|
|
126
|
+
* default config when not provided.
|
|
127
|
+
*/
|
|
128
|
+
config?: SignalIngestionConfig;
|
|
129
|
+
/**
|
|
130
|
+
* Source adapter tier used for base-weight assignment when the signal does
|
|
131
|
+
* not carry `customerTier`. Keyed by adapter name.
|
|
132
|
+
*
|
|
133
|
+
* When not provided, base weight defaults to 1.0 (Tier 1 bias).
|
|
134
|
+
*/
|
|
135
|
+
adapterTiers?: Map<string, SignalTier>;
|
|
136
|
+
/**
|
|
137
|
+
* ICP segment descriptors — the product's declared ideal customer profile.
|
|
138
|
+
* Each entry is a short phrase describing one ICP dimension
|
|
139
|
+
* (e.g. "B2B SaaS startup", "engineering team", "productivity tooling").
|
|
140
|
+
*
|
|
141
|
+
* BM25 similarity between the signal payload and these segments determines
|
|
142
|
+
* ICP resonance. When empty, all signals are classified as `partial`.
|
|
143
|
+
*/
|
|
144
|
+
icpSegments?: string[];
|
|
145
|
+
/**
|
|
146
|
+
* Optional customer-tier registry for `customerId` lookup (tier inference
|
|
147
|
+
* order step 2). When absent, step 2 is skipped.
|
|
148
|
+
*/
|
|
149
|
+
tierRegistry?: CustomerTierRegistry;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Classify a batch of raw signals.
|
|
153
|
+
*
|
|
154
|
+
* Per RFC-0030 §6 inference order for tier:
|
|
155
|
+
* 1. `signal.customerTier` (adapter-provided structured field)
|
|
156
|
+
* 2. `tierRegistry.resolve(signal.customerId)` (registry lookup)
|
|
157
|
+
* 3. Adapter `defaultTier` (mapped: Tier 1 → smb, Tier 2 → free)
|
|
158
|
+
*
|
|
159
|
+
* Per RFC-0030 §13.2: non-accepted-language signals are dropped and
|
|
160
|
+
* returned in `languageDecisions`.
|
|
161
|
+
*/
|
|
162
|
+
export declare function classifySignals(signals: RawSignal[], options?: ClassifySignalsOptions): ClassificationResult;
|
|
163
|
+
/**
|
|
164
|
+
* Resolve customer tier per RFC-0030 §6.1 inference order:
|
|
165
|
+
* 1. `signal.customerTier`
|
|
166
|
+
* 2. `tierRegistry.resolve(signal.customerId)`
|
|
167
|
+
* 3. Adapter defaultTier → smb (Tier 1) or free (Tier 2)
|
|
168
|
+
*/
|
|
169
|
+
export declare function resolveCustomerTier(signal: RawSignal, tierRegistry?: CustomerTierRegistry, adapterTiers?: Map<string, SignalTier>): CustomerTier;
|
|
170
|
+
/**
|
|
171
|
+
* Classify ICP resonance for a signal payload against the declared ICP
|
|
172
|
+
* segment descriptors using BM25 similarity.
|
|
173
|
+
*
|
|
174
|
+
* When `icpSegments` is empty, returns `partial` (no basis for strong/weak).
|
|
175
|
+
*
|
|
176
|
+
* BM25 parameters (k1=1.5, b=0.75) are standard.
|
|
177
|
+
* Score mapping:
|
|
178
|
+
* - ≥ 0.5 → strong
|
|
179
|
+
* - ≥ 0.15 → partial
|
|
180
|
+
* - < 0.15 → weak
|
|
181
|
+
*/
|
|
182
|
+
export declare function resolveIcpResonance(payload: string, icpSegments: string[]): ICPResonance;
|
|
183
|
+
/** Tokenize a string to lowercase alphanumeric tokens, minimum length 2. */
|
|
184
|
+
export declare function tokenize(text: string): string[];
|
|
185
|
+
/**
|
|
186
|
+
* Compute exponential recency decay for a signal.
|
|
187
|
+
*
|
|
188
|
+
* `decay = exp(-age_days * ln(2) / halfLifeDays)`
|
|
189
|
+
*
|
|
190
|
+
* At age = halfLifeDays, decay = 0.5.
|
|
191
|
+
* At age = 0, decay = 1.0.
|
|
192
|
+
* Future-dated signals (age < 0) clamp to 1.0.
|
|
193
|
+
*/
|
|
194
|
+
export declare function computeRecencyDecay(sourceTimestamp: Date, asOf: Date, halfLifeDays: number): number;
|
|
195
|
+
/**
|
|
196
|
+
* Compute the composite signal weight from a `ClassifiedSignal`.
|
|
197
|
+
*
|
|
198
|
+
* `weight = baseWeight × tierMultiplier × icpResonanceWeight × recencyDecay`
|
|
199
|
+
*
|
|
200
|
+
* Note: SA resonance filter (RFC-0030 §9) is applied at the cluster level in
|
|
201
|
+
* Phase 5, not at the signal level here.
|
|
202
|
+
*/
|
|
203
|
+
export declare function computeSignalWeight(classified: ClassifiedSignal): number;
|
|
204
|
+
export type { TierMultipliers, IcpResonanceWeights };
|
|
205
|
+
//# sourceMappingURL=classifier.d.ts.map
|