@fourier-labs/harbour-contracts 0.68.0 → 0.70.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.
@@ -7,7 +7,7 @@ export interface SecurityVerdict {
7
7
  */
8
8
  outcome: 'PASS' | 'BLOCKED';
9
9
  /**
10
- * Policy profile the evidence was evaluated against: the input's `profile` when the caller named one, else derived from the input classification, else STANDARD.
10
+ * Policy profile the evidence was evaluated against, echoed from the input's `profile`.
11
11
  */
12
12
  profile: 'BASELINE' | 'STANDARD' | 'HIGH' | 'CRITICAL';
13
13
  /**
@@ -62,22 +62,28 @@ export interface Finding {
62
62
  * Suggested fix, when the tool provides one.
63
63
  */
64
64
  remediation?: string;
65
+ dependency?: Dependency;
65
66
  /**
66
- * Dependency name, for a dependency-sca finding.
67
+ * Set by policy on every finding it evaluated: true when the finding on its own fires a block rule under the evaluated profile, false when it is reported only.
67
68
  */
68
- package?: string;
69
+ blocking?: boolean;
70
+ }
71
+ /**
72
+ * The dependency a dependency-sca finding is about. Absent on findings that are not about one installed package (secrets, code, a missing lockfile).
73
+ */
74
+ export interface Dependency {
69
75
  /**
70
- * Installed version of `package` the vulnerability applies to.
76
+ * Package name as the lockfile names it.
71
77
  */
72
- installedVersion?: string;
78
+ name: string;
73
79
  /**
74
- * First version of `package` that fixes the vulnerability, when the tool knows one.
80
+ * Version of the package the app installs, which the vulnerability applies to.
75
81
  */
76
- fixedVersion?: string;
82
+ installedVersion: string;
77
83
  /**
78
- * Set by policy on every finding it evaluated: true when the finding on its own fires a block rule under the evaluated profile, false when it is reported only.
84
+ * The one version to upgrade to: the lowest fixed release on the installed version's major line, else the lowest fixed release above the installed version. Absent when no fix above the installed version exists.
79
85
  */
80
- blocking?: boolean;
86
+ fixedVersion?: string;
81
87
  }
82
88
  export interface EvidenceRef {
83
89
  /**
@@ -113,8 +119,4 @@ export interface Provenance {
113
119
  * Orderable release version of the runner image that produced the evidence. Governance compares its numeric core with MINIMUM_SCANNER_VERSION to decide whether a PASS still counts.
114
120
  */
115
121
  scannerVersion: string;
116
- /**
117
- * True iff the input named neither a profile nor a classification and the machine assumed STANDARD (decision 2026-08-27). An invalid classification fails closed and never reaches a verdict.
118
- */
119
- classificationDefaulted: boolean;
120
122
  }
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable */
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
- export const ContractRepositoryVersion = "0.68.0.0" as const;
5
- export const ContractPublishVersion = "0.68.0" as const;
6
- export const ContractPublishTag = "v0.68.0" as const;
7
- export const ContractSchemasDigest = "sha256:3860585b4a4a55334e788faca34018dfaa21bf7ee5fa42bb50b0a454a77047b3" as const;
4
+ export const ContractRepositoryVersion = "0.70.0.0" as const;
5
+ export const ContractPublishVersion = "0.70.0" as const;
6
+ export const ContractPublishTag = "v0.70.0" as const;
7
+ export const ContractSchemasDigest = "sha256:b3c430914fee8a4f33dc15051ec59a6a8bad9a84c31fda119b6d683eb523b094" as const;
8
8
  export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6" as const;
9
- export const ContractGeneratedGoDigest = "sha256:0c97fd145737bf8fba6de780187033f04e52fe524284cd95c084fcda38e31a28" as const;
10
- export const ContractGeneratedTypeScriptDigest = "sha256:bd9927f250e96a91958f078ea29d1420ca3d6545ce93b2b2901033ac09e52827" as const;
9
+ export const ContractGeneratedGoDigest = "sha256:05b383b27a90f3b3495a327e3da9ab6e13c5c2b047fc9311616ff6e7a283ccab" as const;
10
+ export const ContractGeneratedTypeScriptDigest = "sha256:184aacb9eb73cec089f8e891b1840873ca9497eb91cf4d3b12cfa9f0a9f305d1" as const;
@@ -11,7 +11,13 @@
11
11
  export type IntegrationExecuteInputFieldRule =
12
12
  | { kind: 'integer'; required: boolean; minimum: number; maximum: number; default?: number }
13
13
  | { kind: 'string'; required: boolean; minLength: number; maxLength?: number; pattern?: string }
14
- | { kind: 'string[]'; required: boolean; minItems: number; maxItems: number; uniqueItems: boolean; item: { minLength: number; maxLength?: number; pattern?: string } };
14
+ | { kind: 'string[]'; required: boolean; minItems: number; maxItems: number; uniqueItems: boolean; item: { minLength: number; maxLength?: number; pattern?: string } }
15
+ | { kind: 'enum'; required: boolean; values: readonly string[]; default?: string }
16
+ | { kind: 'object[]'; required: boolean; minItems: number; maxItems: number; fields: Readonly<Record<string, IntegrationExecuteInputFieldRule>> }
17
+ | { kind: 'value'; required: boolean; types: readonly IntegrationExecuteInputValueType[]; maxLength?: number; list?: { minItems: number; maxItems: number; types: readonly IntegrationExecuteInputValueType[] } };
18
+
19
+ /** The scalar types a `value` input accepts, alone or in its list form. */
20
+ export type IntegrationExecuteInputValueType = 'string' | 'number' | 'boolean';
15
21
 
16
22
  export type IntegrationExecuteOperationRule = { idempotencyKey: boolean; fields: Record<string, IntegrationExecuteInputFieldRule> };
17
23
 
@@ -54,7 +60,22 @@ export const IntegrationExecuteInputRules = {
54
60
  "warehouse.view.read": {
55
61
  idempotencyKey: false,
56
62
  fields: {
57
- columns: { kind: "string[]", required: true, minItems: 1, maxItems: 32, uniqueItems: false, item: { minLength: 0, pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$" } },
63
+ columns: { kind: "string[]", required: false, minItems: 1, maxItems: 32, uniqueItems: false, item: { minLength: 0, pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$" } },
64
+ where: { kind: "object[]", required: false, minItems: 1, maxItems: 16, fields: {
65
+ column: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" },
66
+ op: { kind: "enum", required: true, values: ["eq", "ne", "gt", "gte", "lt", "lte", "in", "notIn", "isNull", "isNotNull"] },
67
+ value: { kind: "value", required: false, types: ["string", "number", "boolean"], maxLength: 256, list: { minItems: 1, maxItems: 100, types: ["string", "number"] } },
68
+ } },
69
+ groupBy: { kind: "string[]", required: false, minItems: 1, maxItems: 8, uniqueItems: false, item: { minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" } },
70
+ aggregates: { kind: "object[]", required: false, minItems: 1, maxItems: 16, fields: {
71
+ fn: { kind: "enum", required: true, values: ["count", "sum", "avg", "min", "max"] },
72
+ column: { kind: "string", required: true, minLength: 0, pattern: "^(?:\\*|[A-Za-z_][A-Za-z0-9_]{0,62})$" },
73
+ as: { kind: "string", required: true, minLength: 0, pattern: "^[a-z_][a-z0-9_]{0,62}$" },
74
+ } },
75
+ orderBy: { kind: "object[]", required: false, minItems: 1, maxItems: 8, fields: {
76
+ column: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" },
77
+ direction: { kind: "enum", required: false, values: ["asc", "desc"], default: "asc" },
78
+ } },
58
79
  limit: { kind: "integer", required: false, minimum: 1, maximum: 1000, default: 100 },
59
80
  },
60
81
  },
@@ -79,41 +100,95 @@ function matches(pattern: string, value: string): boolean {
79
100
  return compiled.test(value);
80
101
  }
81
102
 
103
+ type Refusal = { ok: false; problem: IntegrationExecuteInputProblem };
104
+ function refuse(field: string, message: string): Refusal { return { ok: false, problem: { field, message } }; }
105
+
106
+ const valueNouns = { string: ['a string', 'strings'], number: ['a number', 'numbers'], boolean: ['a boolean', 'booleans'] } as const;
107
+ function either(parts: readonly string[]): string {
108
+ return parts.length > 1 ? `${parts.slice(0, -1).join(', ')} or ${parts[parts.length - 1]}` : parts.join('');
109
+ }
110
+ function isScalar(types: readonly IntegrationExecuteInputValueType[], maxLength: number | undefined, value: unknown): boolean {
111
+ if (typeof value === 'string') return types.includes('string') && (maxLength === undefined || value.length <= maxLength);
112
+ if (typeof value === 'number') return types.includes('number') && Number.isFinite(value);
113
+ return typeof value === 'boolean' && types.includes('boolean');
114
+ }
115
+
82
116
  /**
83
- * Validates one operation's input against the contract and returns it with the
84
- * contract's defaults applied. The message names the field and its allowed
85
- * range; it is the sentence the platform answers with, so a caller may show it.
117
+ * Checks one object's fields in rule order and applies defaults: the input
118
+ * itself (where null counts as absent), or one entry of an object[] input
119
+ * (where only a missing key is absent, so null is refused like any wrong value).
86
120
  */
87
- export function validateIntegrationExecuteInput(operation: string, input: unknown): IntegrationExecuteInputValidation {
88
- const rule = (IntegrationExecuteInputRules as Record<string, IntegrationExecuteOperationRule>)[operation];
89
- if (!rule) return { ok: false, problem: { field: 'operation', message: `${operation} is not a declared integration operation` } };
90
- const raw = input === undefined ? {} : input;
91
- if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return { ok: false, problem: { field: 'input', message: 'input must be an object' } };
92
- const given = raw as Record<string, unknown>;
93
- for (const name of Object.keys(given)) if (!Object.prototype.hasOwnProperty.call(rule.fields, name)) return { ok: false, problem: { field: name, message: `${name} is not an input of ${operation}` } };
121
+ function checkFields(operation: string, prefix: string, fields: Readonly<Record<string, IntegrationExecuteInputFieldRule>>, given: Record<string, unknown>, nullIsAbsent: boolean): { ok: true; value: Record<string, unknown> } | Refusal {
122
+ for (const name of Object.keys(given)) if (!Object.prototype.hasOwnProperty.call(fields, name)) return refuse(prefix + name, `${prefix}${name} is not an input of ${operation}`);
94
123
  const out: Record<string, unknown> = {};
95
- for (const [name, field] of Object.entries(rule.fields)) {
124
+ for (const [name, field] of Object.entries(fields)) {
125
+ const path = prefix + name;
96
126
  const value = given[name];
97
- if (value === undefined || value === null) {
98
- if (field.required) return { ok: false, problem: { field: name, message: `${name} is required for ${operation}` } };
99
- if (field.kind === 'integer' && field.default !== undefined) out[name] = field.default;
127
+ if (value === undefined || (nullIsAbsent && value === null)) {
128
+ if (field.required) return refuse(path, `${path} is required for ${operation}`);
129
+ if ((field.kind === 'integer' || field.kind === 'enum') && field.default !== undefined) out[name] = field.default;
100
130
  continue;
101
131
  }
102
- if (field.kind === 'integer') {
103
- if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum) return { ok: false, problem: { field: name, message: `${name} must be an integer between ${field.minimum} and ${field.maximum}` } };
104
- } else if (field.kind === 'string') {
105
- if (typeof value !== 'string' || value.length < field.minLength || (field.maxLength !== undefined && value.length > field.maxLength)) return { ok: false, problem: { field: name, message: field.maxLength !== undefined ? `${name} must be ${field.minLength} to ${field.maxLength} characters` : `${name} must be a string of at least ${field.minLength} characters` } };
106
- if (field.pattern && !matches(field.pattern, value)) return { ok: false, problem: { field: name, message: `${name} must match ${field.pattern}` } };
107
- } else {
108
- if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems) return { ok: false, problem: { field: name, message: `${name} must list ${field.minItems} to ${field.maxItems} items` } };
132
+ const checked = checkField(operation, path, name, field, value);
133
+ if (!checked.ok) return checked;
134
+ out[name] = checked.value;
135
+ }
136
+ return { ok: true, value: out };
137
+ }
138
+
139
+ function checkField(operation: string, path: string, name: string, field: IntegrationExecuteInputFieldRule, value: unknown): { ok: true; value: unknown } | Refusal {
140
+ switch (field.kind) {
141
+ case 'integer':
142
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum) return refuse(path, `${path} must be an integer between ${field.minimum} and ${field.maximum}`);
143
+ return { ok: true, value };
144
+ case 'string':
145
+ if (typeof value !== 'string' || value.length < field.minLength || (field.maxLength !== undefined && value.length > field.maxLength)) return refuse(path, field.maxLength !== undefined ? `${path} must be ${field.minLength} to ${field.maxLength} characters` : `${path} must be a string of at least ${field.minLength} characters`);
146
+ if (field.pattern && !matches(field.pattern, value)) return refuse(path, `${path} must match ${field.pattern}`);
147
+ return { ok: true, value };
148
+ case 'enum':
149
+ if (typeof value !== 'string' || !field.values.includes(value)) return refuse(path, `${path} must be one of ${field.values.join(', ')}`);
150
+ return { ok: true, value };
151
+ case 'string[]': {
152
+ if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems) return refuse(path, `${path} must list ${field.minItems} to ${field.maxItems} items`);
109
153
  const seen = new Set<string>();
110
154
  for (const [index, item] of value.entries()) {
111
- if (typeof item !== 'string' || item.length < field.item.minLength || (field.item.maxLength !== undefined && item.length > field.item.maxLength) || (field.item.pattern !== undefined && !matches(field.item.pattern, item))) return { ok: false, problem: { field: name, message: `${name}[${index}] is not a valid ${name} entry` } };
112
- if (field.uniqueItems && seen.has(item)) return { ok: false, problem: { field: name, message: `${name} must not repeat an entry` } };
155
+ if (typeof item !== 'string' || item.length < field.item.minLength || (field.item.maxLength !== undefined && item.length > field.item.maxLength) || (field.item.pattern !== undefined && !matches(field.item.pattern, item))) return refuse(path, `${path}[${index}] is not a valid ${name} entry`);
156
+ if (field.uniqueItems && seen.has(item)) return refuse(path, `${path} must not repeat an entry`);
113
157
  seen.add(item);
114
158
  }
159
+ return { ok: true, value };
160
+ }
161
+ case 'object[]': {
162
+ if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems) return refuse(path, `${path} must list ${field.minItems} to ${field.maxItems} items`);
163
+ const entries: Record<string, unknown>[] = [];
164
+ for (const [index, item] of value.entries()) {
165
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) return refuse(path, `${path}[${index}] is not a valid ${name} entry`);
166
+ const entry = checkFields(operation, `${path}[${index}].`, field.fields, item as Record<string, unknown>, false);
167
+ if (!entry.ok) return entry;
168
+ entries.push(entry.value);
169
+ }
170
+ return { ok: true, value: entries };
171
+ }
172
+ case 'value': {
173
+ const { types, maxLength, list } = field;
174
+ if (isScalar(types, maxLength, value) || (list !== undefined && Array.isArray(value) && value.length >= list.minItems && value.length <= list.maxItems && value.every(item => isScalar(list.types, maxLength, item)))) return { ok: true, value };
175
+ const shapes = types.map(type => (type === 'string' && maxLength !== undefined ? `a string of at most ${maxLength} characters` : valueNouns[type][0]));
176
+ if (list !== undefined) shapes.push(`a list of ${list.minItems} to ${list.maxItems} ${either(list.types.map(type => valueNouns[type][1]))}`);
177
+ return refuse(path, `${path} must be ${either(shapes)}`);
115
178
  }
116
- out[name] = value;
117
179
  }
118
- return { ok: true, input: out };
180
+ }
181
+
182
+ /**
183
+ * Validates one operation's input against the contract and returns it with the
184
+ * contract's defaults applied. The message names the field and its allowed
185
+ * range; it is the sentence the platform answers with, so a caller may show it.
186
+ */
187
+ export function validateIntegrationExecuteInput(operation: string, input: unknown): IntegrationExecuteInputValidation {
188
+ const rule = (IntegrationExecuteInputRules as Record<string, IntegrationExecuteOperationRule>)[operation];
189
+ if (!rule) return refuse('operation', `${operation} is not a declared integration operation`);
190
+ const raw = input === undefined ? {} : input;
191
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return refuse('input', 'input must be an object');
192
+ const checked = checkFields(operation, '', rule.fields, raw as Record<string, unknown>, true);
193
+ return checked.ok ? { ok: true, input: checked.value } : checked;
119
194
  }
@@ -2,7 +2,7 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is fixed by the operation for every operation but slack.message.post, and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (the IT-approved per-app name from the grant, plus a "Posted by <app> on Isomorph" footer) or as the signed-in person (no name, no footer) — the same input either way — and since 0.55.0 the call names which of the app's approved modes it runs under (`mode`): required when IT approved both modes for the app (MODE_REQUIRED otherwise), optional when it approved one (that one is used), and a named mode without an approved grant is refused (MODE_NOT_GRANTED) rather than served under the other, so approval order never decides what a post does. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. Each operation's then-block is the one statement of its input rules: propertyNames.enum names the fields it accepts, required the ones it needs, and per-field overrides (maximum, default) narrow the shared property. Codegen derives IntegrationExecuteInputRules and validateIntegrationExecuteInput (gen-ts/integration-execute-input-rules.ts, gen-go/integration_execute_input_rules.go) from exactly these blocks, so the governance execute route, the app SDK and the kit's local check refuse the same request with the same message. Transport limits (not expressible here): request <= 16 KiB, total deadline 10 s.
5
+ * Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is fixed by the operation for every operation but slack.message.post, and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (the IT-approved per-app name from the grant, plus a "Posted by <app> on Isomorph" footer) or as the signed-in person (no name, no footer) — the same input either way — and since 0.55.0 the call names which of the app's approved modes it runs under (`mode`): required when IT approved both modes for the app (MODE_REQUIRED otherwise), optional when it approved one (that one is used), and a named mode without an approved grant is refused (MODE_NOT_GRANTED) rather than served under the other, so approval order never decides what a post does. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns? 1..32, where? 1..16 filters, groupBy? 1..8 columns, aggregates? 1..16, orderBy? 1..8, limit? 1..1000, default 100} (0.70.0: a plain read names columns, an aggregate read names aggregates and optionally groupBy; the cross-field rules — exactly one of columns or aggregates, groupBy needs aggregates, a filter value that fits its op, orderBy naming a returned column, every named column approved — are enforced by governance, not expressible here; ADR 0017). Each operation's then-block is the one statement of its input rules: propertyNames.enum names the fields it accepts, required the ones it needs, and per-field overrides (maximum, default) narrow the shared property. Codegen derives IntegrationExecuteInputRules and validateIntegrationExecuteInput (gen-ts/integration-execute-input-rules.ts, gen-go/integration_execute_input_rules.go) from exactly these blocks, so the governance execute route, the app SDK and the kit's local check refuse the same request with the same message. Transport limits (not expressible here): request <= 16 KiB, total deadline 10 s.
6
6
  */
7
7
  export type IntegrationExecuteRequest = {
8
8
  /**
@@ -48,12 +48,40 @@ export type IntegrationExecuteRequest = {
48
48
  */
49
49
  replyToMessageId?: string;
50
50
  /**
51
- * Explicit columns, or `*` alone when the app's approved resource grant allows all current and future columns.
51
+ * warehouse.view.read, a plain read: explicit columns, or `*` alone when the app's approved resource grant allows all current and future columns. An aggregate read names aggregates instead.
52
52
  *
53
53
  * @minItems 1
54
54
  * @maxItems 32
55
55
  */
56
56
  columns?: string[];
57
+ /**
58
+ * warehouse.view.read (0.70.0): filters the warehouse applies before it returns, grouping or limits rows. All of them must hold (AND); there is no OR and no LIKE.
59
+ *
60
+ * @minItems 1
61
+ * @maxItems 16
62
+ */
63
+ where?: WarehouseFilter[];
64
+ /**
65
+ * warehouse.view.read (0.70.0): columns (never `*`) that group the rows; needs aggregates. Each returned row is one group: its groupBy columns, then its aggregate aliases.
66
+ *
67
+ * @minItems 1
68
+ * @maxItems 8
69
+ */
70
+ groupBy?: string[];
71
+ /**
72
+ * warehouse.view.read (0.70.0): what an aggregate read computes, over each group or over every matching row when there is no groupBy.
73
+ *
74
+ * @minItems 1
75
+ * @maxItems 16
76
+ */
77
+ aggregates?: WarehouseAggregate[];
78
+ /**
79
+ * warehouse.view.read (0.70.0): sort keys, first key first. Without orderBy an aggregate read is sorted by its groupBy columns and a plain read is not sorted at all.
80
+ *
81
+ * @minItems 1
82
+ * @maxItems 8
83
+ */
84
+ orderBy?: WarehouseOrder[];
57
85
  };
58
86
  /**
59
87
  * UUID of one explicit Send action; required for slack.message.post and gmail.message.send only. The same key with the same payload and subject returns the recorded outcome for seven days.
@@ -89,3 +117,54 @@ export type EmailAddressList1 = EmailAddress[];
89
117
  * @maxItems 50
90
118
  */
91
119
  export type EmailAddressList2 = EmailAddress[];
120
+ /**
121
+ * A filter comparison: = <> > >= < <= IN, NOT IN, IS NULL, IS NOT NULL. Word-like so every value is a valid generated constant.
122
+ */
123
+ export type WarehouseFilterOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'isNull' | 'isNotNull';
124
+ /**
125
+ * An aggregate function.
126
+ */
127
+ export type WarehouseAggregateFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
128
+ /**
129
+ * Sort direction; asc when absent.
130
+ */
131
+ export type WarehouseOrderDirection = 'asc' | 'desc';
132
+
133
+ /**
134
+ * One warehouse.view.read filter (0.70.0): <column> <op> <value>. The value is always bound as a query parameter, never written into the SQL. Which value an op takes is a governance rule: isNull/isNotNull take none; in/notIn take a non-empty list of one scalar type; every other op takes one scalar, and a boolean only with eq/ne.
135
+ */
136
+ export interface WarehouseFilter {
137
+ /**
138
+ * The filtered column; never `*`.
139
+ */
140
+ column: string;
141
+ op: WarehouseFilterOp;
142
+ /**
143
+ * The value compared with: a string (a date or timestamp is its ISO string), a number, a boolean, or a list of strings or numbers for in/notIn. Never null; use isNull or isNotNull.
144
+ */
145
+ value?: string | number | boolean | (string | number)[];
146
+ }
147
+ /**
148
+ * One computed column of an aggregate warehouse.view.read (0.70.0): fn(column) AS as. `*` is a column only for count (a governance rule). The value follows the read's cell rules, so a 64-bit or decimal result may be a string.
149
+ */
150
+ export interface WarehouseAggregate {
151
+ fn: WarehouseAggregateFn;
152
+ /**
153
+ * A plain warehouse column identifier, or `*` as the sole selection when IT exposed all current and future columns.
154
+ */
155
+ column: string;
156
+ /**
157
+ * The name the computed value is returned under; unique in the read and never a groupBy column (governance rules).
158
+ */
159
+ as: string;
160
+ }
161
+ /**
162
+ * One warehouse.view.read sort key (0.70.0). A plain read sorts by a selected column (any column when it selects `*`); an aggregate read sorts by a groupBy column or an aggregate alias (governance rules).
163
+ */
164
+ export interface WarehouseOrder {
165
+ /**
166
+ * A column or an aggregate alias; never `*`.
167
+ */
168
+ column: string;
169
+ direction?: WarehouseOrderDirection;
170
+ }
@@ -2,7 +2,7 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time, plus the display name and emoji icon a per-app-named app post carries — authorName/authorIcon, 0.55.0 — never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.
5
+ * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time, plus the display name and emoji icon a per-app-named app post carries — authorName/authorIcon, 0.55.0 — never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows, truncated} (rows keyed by the selected columns, or for an aggregate read by the groupBy columns then the aggregate aliases; decimal/64-bit values as strings, dates as ISO strings; since 0.70.0 every server sends `truncated`, true when more rows matched than `limit` — the server reads limit+1 rows to know — so the rows are incomplete and must not be totalled or averaged; a server before 0.70.0 omitted it). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. A whole result over 1 MiB is rejected, never cut down to fit.
6
6
  */
7
7
  export type IntegrationExecuteResult = {
8
8
  /**
@@ -22,6 +22,9 @@ export type IntegrationExecuteResult = {
22
22
  to?: string;
23
23
  date?: string;
24
24
  text?: string;
25
+ /**
26
+ * gmail.message.read: the text was cut at 16 KiB. warehouse.view.read (0.70.0): more rows matched than the read's limit, so `rows` is incomplete.
27
+ */
25
28
  truncated?: boolean;
26
29
  /**
27
30
  * @maxItems 1000
@@ -2,7 +2,7 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * Input to the Security Assurance machine, second shape, replacing security-scan-input.schema.json. V1 scanned a deployed commit located through the deployment state; v2 scans exactly one thing: an uploaded package that governance validated at source intake, identified by the zip sha256 the stager verifies before extraction and by the tree digest governance computed from the unzipped bytes (`sourceDigestOf`, `sha256:`-prefixed), which keys the governance scan record and the check report on the laptop. Commit-mode and ARTIFACT-phase inputs are deliberately absent: the console's later on-demand scan uses this same package path against the deploy upload, and the Transform lane runs the scanner in-process; an image scan, if it returns, is an additive v3. The policy profile is named by the caller when it knows better than the data classification (the kit lane names BASELINE); `profile` and `classification` are mutually exclusive, and both absent still defaults to STANDARD with `classificationDefaulted: true`. Nothing here carries a scanner version: the scanner stamps its own into verdict provenance, and governance decides validity by comparing that against its MINIMUM_SCANNER_VERSION. `org`/`app` are the tenant and app ids for kit apps; `env` stays required because the evidence key carries it (governance passes `preview` for a package scan; promotion never re-scans). The `execution` block names the tenant's execution-account scanner, invoke role and artifacts bucket, resolved by governance the way admission resolves them for the deploy machine.
5
+ * Input to the Security Assurance machine, second shape, replacing security-scan-input.schema.json. V1 scanned a deployed commit located through the deployment state; v2 scans exactly one thing: an uploaded package that governance validated at source intake, identified by the zip sha256 the stager verifies before extraction and by the tree digest governance computed from the unzipped bytes (`sourceDigestOf`, `sha256:`-prefixed), which keys the governance scan record and the check report on the laptop. Commit-mode and ARTIFACT-phase inputs are deliberately absent: the console's later on-demand scan uses this same package path against the deploy upload, and the Transform lane runs the scanner in-process; an image scan, if it returns, is an additive v3. The caller always names the policy profile (governance names BASELINE for the kit lane); there is no classification input and no default profile. Nothing here carries a scanner version: the scanner stamps its own into verdict provenance, and governance decides validity by comparing that against its MINIMUM_SCANNER_VERSION. `org`/`app` are the tenant and app ids for kit apps; `env` stays required because the evidence key carries it (governance passes `preview` for a package scan; promotion never re-scans). The `execution` block names the tenant's execution-account scanner, invoke role and artifacts bucket, resolved by governance the way admission resolves them for the deploy machine.
6
6
  */
7
7
  export interface SecurityScanInputV2 {
8
8
  schema: 'harbour.security-scan-input/2.0';
@@ -21,13 +21,9 @@ export interface SecurityScanInputV2 {
21
21
  package: PackageSource;
22
22
  execution: Execution;
23
23
  /**
24
- * Policy profile named by the caller. Takes the place of deriving one from `classification`; the verdict echoes the profile evaluated and stamps `classificationDefaulted: false`. Governance names BASELINE for kit-lane scans; the default when nothing is named is STANDARD.
24
+ * Policy profile the machine evaluates the evidence against; the verdict echoes it. Governance names BASELINE for kit-lane scans.
25
25
  */
26
- profile?: 'BASELINE' | 'STANDARD' | 'HIGH' | 'CRITICAL';
27
- /**
28
- * Data classification, mapped to a profile as in v1 (GREEN → BASELINE, YELLOW → STANDARD, ORANGE → HIGH, RED → CRITICAL). Not allowed beside `profile`. Absent with no `profile` means STANDARD and `classificationDefaulted: true`; an invalid value fails closed.
29
- */
30
- classification?: 'GREEN' | 'YELLOW' | 'ORANGE' | 'RED';
26
+ profile: 'BASELINE' | 'STANDARD' | 'HIGH' | 'CRITICAL';
31
27
  }
32
28
  /**
33
29
  * The bytes `isomorph secure` (or a later console re-run against a deploy upload) put through source intake.
@@ -10,7 +10,7 @@ export interface SecurityVerdict {
10
10
  */
11
11
  outcome: 'PASS' | 'BLOCKED';
12
12
  /**
13
- * Policy profile the evidence was evaluated against: the input's `profile` when the caller named one, else derived from the input classification, else STANDARD.
13
+ * Policy profile the evidence was evaluated against, echoed from the input's `profile`.
14
14
  */
15
15
  profile: 'BASELINE' | 'STANDARD' | 'HIGH' | 'CRITICAL';
16
16
  /**
@@ -65,22 +65,28 @@ export interface Finding {
65
65
  * Suggested fix, when the tool provides one.
66
66
  */
67
67
  remediation?: string;
68
+ dependency?: Dependency;
68
69
  /**
69
- * Dependency name, for a dependency-sca finding.
70
+ * Set by policy on every finding it evaluated: true when the finding on its own fires a block rule under the evaluated profile, false when it is reported only.
70
71
  */
71
- package?: string;
72
+ blocking?: boolean;
73
+ }
74
+ /**
75
+ * The dependency a dependency-sca finding is about. Absent on findings that are not about one installed package (secrets, code, a missing lockfile).
76
+ */
77
+ export interface Dependency {
72
78
  /**
73
- * Installed version of `package` the vulnerability applies to.
79
+ * Package name as the lockfile names it.
74
80
  */
75
- installedVersion?: string;
81
+ name: string;
76
82
  /**
77
- * First version of `package` that fixes the vulnerability, when the tool knows one.
83
+ * Version of the package the app installs, which the vulnerability applies to.
78
84
  */
79
- fixedVersion?: string;
85
+ installedVersion: string;
80
86
  /**
81
- * Set by policy on every finding it evaluated: true when the finding on its own fires a block rule under the evaluated profile, false when it is reported only.
87
+ * The one version to upgrade to: the lowest fixed release on the installed version's major line, else the lowest fixed release above the installed version. Absent when no fix above the installed version exists.
82
88
  */
83
- blocking?: boolean;
89
+ fixedVersion?: string;
84
90
  }
85
91
  export interface EvidenceRef {
86
92
  /**
@@ -116,8 +122,4 @@ export interface Provenance {
116
122
  * Orderable release version of the runner image that produced the evidence. Governance compares its numeric core with MINIMUM_SCANNER_VERSION to decide whether a PASS still counts.
117
123
  */
118
124
  scannerVersion: string;
119
- /**
120
- * True iff the input named neither a profile nor a classification and the machine assumed STANDARD (decision 2026-08-27). An invalid classification fails closed and never reaches a verdict.
121
- */
122
- classificationDefaulted: boolean;
123
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fourier-labs/harbour-contracts",
3
- "version": "0.68.0",
3
+ "version": "0.70.0",
4
4
  "description": "Single source of truth for every data shape that crosses a boundary between Isomorph components.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,