@unson/brainbase-mcp 0.23.1 → 0.23.3
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/company-os-judgment-history.d.ts +157 -0
- package/dist/company-os-judgment-history.js +489 -0
- package/dist/judgment-history-http.d.ts +18 -0
- package/dist/judgment-history-http.js +170 -0
- package/dist/judgment-history.d.ts +178 -0
- package/dist/judgment-history.js +1519 -0
- package/dist/knowledge-continuation.js +1 -1
- package/dist/knowledge-lookup.js +1 -1
- package/dist/local-web-host.d.ts +8 -0
- package/dist/local-web-host.js +36 -0
- package/dist/problem-selection.d.ts +84 -1
- package/dist/problem-selection.js +141 -15
- package/package.json +14 -2
- package/ui/judgment-history.css +173 -0
- package/ui/judgment-history.js +1077 -32
- package/ui/local-web-shell.js +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { JudgmentViewAccessContext, JudgmentViewDocument } from './judgment-view.js';
|
|
2
|
+
import { type JudgmentHistoryAlternative, type JudgmentHistoryExecutionStatus, type JudgmentHistoryRecord, type JudgmentHistoryReference, type JudgmentHistorySource } from './judgment-history.js';
|
|
3
|
+
/**
|
|
4
|
+
* The implementation contract for the Company OS slice of the normal
|
|
5
|
+
* judgment-history reader.
|
|
6
|
+
*
|
|
7
|
+
* This module is deliberately a projection adapter. It does not create a
|
|
8
|
+
* catalog, copy a run, or persist a second history. A host supplies a
|
|
9
|
+
* trusted `listRun` port backed by the existing Composition run/judgment-view
|
|
10
|
+
* source and keeps the owner/scope check at that source boundary.
|
|
11
|
+
*
|
|
12
|
+
* @see brainbase-project/docs/specs/judgment-history-end-to-end-v1.md
|
|
13
|
+
* @see brainbase-project/docs/architecture/judgment-history-recording-boundary-v1.md
|
|
14
|
+
*/
|
|
15
|
+
export declare const COMPANY_OS_JUDGMENT_HISTORY_ADAPTER_VERSION: "company-os-judgment-history.v1";
|
|
16
|
+
export declare const COMPANY_OS_JUDGMENT_HISTORY_SPEC_REFERENCE: "brainbase-project/docs/specs/judgment-history-end-to-end-v1.md";
|
|
17
|
+
export declare const COMPANY_OS_JUDGMENT_HISTORY_RECORD_SCHEMA: "brainbase-judgment-history-record-v1";
|
|
18
|
+
export declare const COMPANY_OS_JUDGMENT_HISTORY_CONTRACT_VERSION: "brainbase.judgment-history.v1";
|
|
19
|
+
export type CompanyOsJudgmentHistoryListStatus = 'available' | 'partial' | 'unavailable';
|
|
20
|
+
export type CompanyOsJudgmentHistoryEntrypoint = 'company_os';
|
|
21
|
+
export type CompanyOsJudgmentHistoryExecutionStatus = JudgmentHistoryExecutionStatus;
|
|
22
|
+
export type CompanyOsJudgmentHistoryOutcomeStatus = 'unconfirmed' | 'confirmed' | 'unknown';
|
|
23
|
+
export type CompanyOsJudgmentHistoryReferenceAvailability = 'recorded' | 'permission_denied' | 'unavailable' | 'unknown';
|
|
24
|
+
export type CompanyOsJudgmentHistoryReference = JudgmentHistoryReference;
|
|
25
|
+
export type CompanyOsJudgmentHistoryAlternative = JudgmentHistoryAlternative;
|
|
26
|
+
export interface CompanyOsStoredJudgment {
|
|
27
|
+
readonly status?: string | null;
|
|
28
|
+
readonly summary?: string | null;
|
|
29
|
+
readonly reason?: string | null;
|
|
30
|
+
readonly selected_references?: readonly CompanyOsJudgmentHistoryReference[] | null;
|
|
31
|
+
readonly alternatives?: readonly CompanyOsJudgmentHistoryAlternative[] | null;
|
|
32
|
+
}
|
|
33
|
+
export interface CompanyOsStoredExecution {
|
|
34
|
+
readonly status?: CompanyOsJudgmentHistoryExecutionStatus | null;
|
|
35
|
+
readonly result_summary?: string | null;
|
|
36
|
+
readonly outcome_status?: CompanyOsJudgmentHistoryOutcomeStatus | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Public fields already written by the Company OS source. Free-form source
|
|
40
|
+
* text is accepted only through these fields; the adapter never copies a
|
|
41
|
+
* conclusion, child-run output, or internal execution trace into a summary.
|
|
42
|
+
*/
|
|
43
|
+
export interface CompanyOsJudgmentHistorySourceRun {
|
|
44
|
+
/** Stable source identity. If omitted, the historical view's runId is used. */
|
|
45
|
+
readonly source_id?: string;
|
|
46
|
+
/** Stored timestamp; the adapter does not replace it with the current time. */
|
|
47
|
+
readonly recorded_at: string;
|
|
48
|
+
/** Null means the source explicitly recorded that the project is unknown. */
|
|
49
|
+
readonly project_code?: string | null;
|
|
50
|
+
/** Exact source turn identity. The common record requires this field. */
|
|
51
|
+
readonly turn_ref: string;
|
|
52
|
+
/** The source owner must exactly match the trusted request access context. */
|
|
53
|
+
readonly owner: JudgmentViewAccessContext;
|
|
54
|
+
/** Historical view resolved from the same source run and owner scope. */
|
|
55
|
+
readonly view: JudgmentViewDocument;
|
|
56
|
+
/** Explicit public judgment fields, when the source recorded them. */
|
|
57
|
+
readonly public_judgment?: CompanyOsStoredJudgment;
|
|
58
|
+
/** Explicit public execution fields, when the source recorded them. */
|
|
59
|
+
readonly public_execution?: CompanyOsStoredExecution;
|
|
60
|
+
}
|
|
61
|
+
export interface CompanyOsJudgmentHistoryListRunRequest {
|
|
62
|
+
readonly access: JudgmentViewAccessContext;
|
|
63
|
+
readonly period?: 'week' | 'past30days' | 'all';
|
|
64
|
+
readonly project?: string;
|
|
65
|
+
readonly entrypoint?: CompanyOsJudgmentHistoryEntrypoint;
|
|
66
|
+
readonly cursor?: string;
|
|
67
|
+
readonly limit?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* This is the only source-side list dependency. The host owns enumeration,
|
|
71
|
+
* current ACL checks, and source snapshot/cursor semantics. The adapter does
|
|
72
|
+
* not discover repositories or fall back to a service identity.
|
|
73
|
+
*/
|
|
74
|
+
export interface CompanyOsJudgmentHistoryListRunPort {
|
|
75
|
+
readonly listRun: (request: CompanyOsJudgmentHistoryListRunRequest) => Promise<CompanyOsJudgmentHistoryListRunResult> | CompanyOsJudgmentHistoryListRunResult;
|
|
76
|
+
}
|
|
77
|
+
export interface CompanyOsJudgmentHistoryListRunSuccess {
|
|
78
|
+
readonly status?: Exclude<CompanyOsJudgmentHistoryListStatus, 'unavailable'>;
|
|
79
|
+
readonly runs: readonly CompanyOsJudgmentHistorySourceRun[];
|
|
80
|
+
readonly complete?: boolean;
|
|
81
|
+
readonly total?: number | null;
|
|
82
|
+
readonly storage?: 'local' | 'server' | null;
|
|
83
|
+
readonly reason?: string | null;
|
|
84
|
+
/** Stable source revision for cursor binding, when the host can provide it. */
|
|
85
|
+
readonly snapshot_id?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface CompanyOsJudgmentHistoryListRunUnavailable {
|
|
88
|
+
readonly status: 'unavailable';
|
|
89
|
+
readonly runs?: null;
|
|
90
|
+
readonly complete?: false;
|
|
91
|
+
readonly total?: null;
|
|
92
|
+
readonly storage?: 'local' | 'server' | null;
|
|
93
|
+
readonly reason?: string | null;
|
|
94
|
+
}
|
|
95
|
+
export type CompanyOsJudgmentHistoryListRunResult = readonly CompanyOsJudgmentHistorySourceRun[] | CompanyOsJudgmentHistoryListRunSuccess | CompanyOsJudgmentHistoryListRunUnavailable;
|
|
96
|
+
export type CompanyOsJudgmentHistoryRecord = JudgmentHistoryRecord;
|
|
97
|
+
export interface CompanyOsJudgmentHistoryCoverage {
|
|
98
|
+
readonly complete: boolean;
|
|
99
|
+
readonly storage: 'local' | 'server' | null;
|
|
100
|
+
readonly reason: string | null;
|
|
101
|
+
readonly sources: readonly [
|
|
102
|
+
{
|
|
103
|
+
readonly entrypoint: CompanyOsJudgmentHistoryEntrypoint;
|
|
104
|
+
readonly status: CompanyOsJudgmentHistoryListStatus;
|
|
105
|
+
readonly reason: string | null;
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
readonly total: number | null;
|
|
109
|
+
}
|
|
110
|
+
export interface CompanyOsJudgmentHistoryList {
|
|
111
|
+
readonly contract_version: typeof COMPANY_OS_JUDGMENT_HISTORY_CONTRACT_VERSION;
|
|
112
|
+
readonly status: CompanyOsJudgmentHistoryListStatus;
|
|
113
|
+
readonly records: readonly CompanyOsJudgmentHistoryRecord[];
|
|
114
|
+
readonly coverage: CompanyOsJudgmentHistoryCoverage;
|
|
115
|
+
readonly pagination: {
|
|
116
|
+
readonly next_cursor: string | null;
|
|
117
|
+
readonly previous_cursor: string | null;
|
|
118
|
+
readonly limit: number | null;
|
|
119
|
+
};
|
|
120
|
+
readonly filters: {
|
|
121
|
+
readonly period: 'week' | 'past30days' | 'all' | null;
|
|
122
|
+
readonly project: string | null;
|
|
123
|
+
readonly entrypoint: CompanyOsJudgmentHistoryEntrypoint;
|
|
124
|
+
};
|
|
125
|
+
/** Source revision used by the common reader to bind pagination cursors. */
|
|
126
|
+
readonly snapshot_id?: string;
|
|
127
|
+
}
|
|
128
|
+
export interface CompanyOsJudgmentHistoryAdapter {
|
|
129
|
+
readonly list: (request: CompanyOsJudgmentHistoryListRunRequest) => Promise<CompanyOsJudgmentHistoryList>;
|
|
130
|
+
}
|
|
131
|
+
export interface CompanyOsJudgmentHistoryAdapterOptions {
|
|
132
|
+
readonly sourcePort?: CompanyOsJudgmentHistoryListRunPort;
|
|
133
|
+
}
|
|
134
|
+
export interface CompanyOsJudgmentHistorySourceOptions {
|
|
135
|
+
/** Trusted context captured by the host before constructing this source. */
|
|
136
|
+
readonly access: JudgmentViewAccessContext;
|
|
137
|
+
readonly sourcePort?: CompanyOsJudgmentHistoryListRunPort;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Stable identity helper shared with Host injection tests. It is derived
|
|
141
|
+
* only from the owner binding and source identity; content and current time
|
|
142
|
+
* are intentionally excluded so retries cannot replace an old record.
|
|
143
|
+
*/
|
|
144
|
+
export declare function companyOsJudgmentHistoryRecordId(owner: JudgmentViewAccessContext, sourceId: string): string;
|
|
145
|
+
export declare function createCompanyOsJudgmentHistoryAdapter(options?: CompanyOsJudgmentHistoryAdapterOptions): CompanyOsJudgmentHistoryAdapter;
|
|
146
|
+
/**
|
|
147
|
+
* Builds the common reader source expected by `createJudgmentHistoryReader`.
|
|
148
|
+
* The access context is intentionally construction-time state: the common
|
|
149
|
+
* source interface has a zero-argument `read`, so a caller-provided query
|
|
150
|
+
* cannot widen the owner or scope after the trusted host boundary.
|
|
151
|
+
*/
|
|
152
|
+
export declare function createCompanyOsJudgmentHistorySource(options: CompanyOsJudgmentHistorySourceOptions): JudgmentHistorySource;
|
|
153
|
+
/**
|
|
154
|
+
* Projects existing Company OS runs to the common list shape. A missing port
|
|
155
|
+
* is an explicit unavailable source, never an empty successful history.
|
|
156
|
+
*/
|
|
157
|
+
export declare function readCompanyOsJudgmentHistory(sourcePort: CompanyOsJudgmentHistoryListRunPort | undefined, request: CompanyOsJudgmentHistoryListRunRequest): Promise<CompanyOsJudgmentHistoryList>;
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { JUDGMENT_HISTORY_CONTRACT_VERSION, JUDGMENT_HISTORY_RECORD_SCHEMA, } from './judgment-history.js';
|
|
2
|
+
/**
|
|
3
|
+
* The implementation contract for the Company OS slice of the normal
|
|
4
|
+
* judgment-history reader.
|
|
5
|
+
*
|
|
6
|
+
* This module is deliberately a projection adapter. It does not create a
|
|
7
|
+
* catalog, copy a run, or persist a second history. A host supplies a
|
|
8
|
+
* trusted `listRun` port backed by the existing Composition run/judgment-view
|
|
9
|
+
* source and keeps the owner/scope check at that source boundary.
|
|
10
|
+
*
|
|
11
|
+
* @see brainbase-project/docs/specs/judgment-history-end-to-end-v1.md
|
|
12
|
+
* @see brainbase-project/docs/architecture/judgment-history-recording-boundary-v1.md
|
|
13
|
+
*/
|
|
14
|
+
export const COMPANY_OS_JUDGMENT_HISTORY_ADAPTER_VERSION = 'company-os-judgment-history.v1';
|
|
15
|
+
export const COMPANY_OS_JUDGMENT_HISTORY_SPEC_REFERENCE = 'brainbase-project/docs/specs/judgment-history-end-to-end-v1.md';
|
|
16
|
+
export const COMPANY_OS_JUDGMENT_HISTORY_RECORD_SCHEMA = JUDGMENT_HISTORY_RECORD_SCHEMA;
|
|
17
|
+
export const COMPANY_OS_JUDGMENT_HISTORY_CONTRACT_VERSION = JUDGMENT_HISTORY_CONTRACT_VERSION;
|
|
18
|
+
const EXECUTION_STATUSES = [
|
|
19
|
+
'pending',
|
|
20
|
+
'completed',
|
|
21
|
+
'failed',
|
|
22
|
+
'held',
|
|
23
|
+
'cancelled',
|
|
24
|
+
'unknown',
|
|
25
|
+
];
|
|
26
|
+
const OUTCOME_STATUSES = [
|
|
27
|
+
'unconfirmed',
|
|
28
|
+
'confirmed',
|
|
29
|
+
'unknown',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Stable identity helper shared with Host injection tests. It is derived
|
|
33
|
+
* only from the owner binding and source identity; content and current time
|
|
34
|
+
* are intentionally excluded so retries cannot replace an old record.
|
|
35
|
+
*/
|
|
36
|
+
export function companyOsJudgmentHistoryRecordId(owner, sourceId) {
|
|
37
|
+
return [
|
|
38
|
+
'company_os',
|
|
39
|
+
encodeURIComponent(owner.tenantId),
|
|
40
|
+
encodeURIComponent(owner.scopeId),
|
|
41
|
+
encodeURIComponent(sourceId),
|
|
42
|
+
].join(':');
|
|
43
|
+
}
|
|
44
|
+
export function createCompanyOsJudgmentHistoryAdapter(options = {}) {
|
|
45
|
+
return {
|
|
46
|
+
async list(request) {
|
|
47
|
+
return readCompanyOsJudgmentHistory(options.sourcePort, request);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds the common reader source expected by `createJudgmentHistoryReader`.
|
|
53
|
+
* The access context is intentionally construction-time state: the common
|
|
54
|
+
* source interface has a zero-argument `read`, so a caller-provided query
|
|
55
|
+
* cannot widen the owner or scope after the trusted host boundary.
|
|
56
|
+
*/
|
|
57
|
+
export function createCompanyOsJudgmentHistorySource(options) {
|
|
58
|
+
assertAccess(options.access);
|
|
59
|
+
return {
|
|
60
|
+
storage: 'server',
|
|
61
|
+
async read() {
|
|
62
|
+
const result = await readCompanyOsJudgmentHistory(options.sourcePort, {
|
|
63
|
+
access: options.access,
|
|
64
|
+
period: 'all',
|
|
65
|
+
entrypoint: 'company_os',
|
|
66
|
+
limit: 100,
|
|
67
|
+
});
|
|
68
|
+
const storage = 'server';
|
|
69
|
+
const source = result.coverage.sources[0];
|
|
70
|
+
if (result.status === 'unavailable') {
|
|
71
|
+
return {
|
|
72
|
+
status: 'unavailable',
|
|
73
|
+
records: null,
|
|
74
|
+
coverage: {
|
|
75
|
+
complete: false,
|
|
76
|
+
storage,
|
|
77
|
+
reason: result.coverage.reason,
|
|
78
|
+
sources: [{ entrypoint: 'company_os', status: 'unavailable', reason: result.coverage.reason }],
|
|
79
|
+
total: null,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
status: result.status,
|
|
85
|
+
records: result.records,
|
|
86
|
+
coverage: {
|
|
87
|
+
complete: result.coverage.complete,
|
|
88
|
+
storage,
|
|
89
|
+
reason: result.coverage.reason,
|
|
90
|
+
sources: [{
|
|
91
|
+
entrypoint: 'company_os',
|
|
92
|
+
status: source?.status ?? result.status,
|
|
93
|
+
reason: source?.reason ?? result.coverage.reason,
|
|
94
|
+
}],
|
|
95
|
+
total: result.coverage.total,
|
|
96
|
+
},
|
|
97
|
+
snapshot_id: result.snapshot_id,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Projects existing Company OS runs to the common list shape. A missing port
|
|
104
|
+
* is an explicit unavailable source, never an empty successful history.
|
|
105
|
+
*/
|
|
106
|
+
export async function readCompanyOsJudgmentHistory(sourcePort, request) {
|
|
107
|
+
assertAccess(request.access);
|
|
108
|
+
const filters = {
|
|
109
|
+
period: request.period ?? null,
|
|
110
|
+
project: request.project ?? null,
|
|
111
|
+
entrypoint: 'company_os',
|
|
112
|
+
};
|
|
113
|
+
if (!sourcePort || typeof sourcePort.listRun !== 'function') {
|
|
114
|
+
return unavailableList(filters, 'company_os_judgment_history_source_not_injected');
|
|
115
|
+
}
|
|
116
|
+
let sourceResult;
|
|
117
|
+
try {
|
|
118
|
+
sourceResult = await sourcePort.listRun(request);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return unavailableList(filters, 'company_os_judgment_history_source_read_failed');
|
|
122
|
+
}
|
|
123
|
+
const normalized = normalizeSourceResult(sourceResult);
|
|
124
|
+
if (normalized.status === 'unavailable') {
|
|
125
|
+
return unavailableList(filters, normalized.reason ?? 'company_os_judgment_history_source_unavailable', {
|
|
126
|
+
storage: normalized.storage,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const records = [];
|
|
130
|
+
let rejected = 0;
|
|
131
|
+
for (const sourceRun of normalized.runs) {
|
|
132
|
+
const projection = projectSourceRun(sourceRun, request.access);
|
|
133
|
+
if (projection.record) {
|
|
134
|
+
records.push(projection.record);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
rejected += 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const rejectedReason = rejected > 0 ? 'company_os_judgment_history_source_record_invalid_or_scope_mismatch' : null;
|
|
141
|
+
const status = normalized.status === 'partial' || rejected > 0 ? 'partial' : 'available';
|
|
142
|
+
const complete = normalized.complete && rejected === 0;
|
|
143
|
+
const total = complete
|
|
144
|
+
? normalized.total ?? records.length
|
|
145
|
+
: null;
|
|
146
|
+
const reason = rejectedReason ?? normalized.reason ?? null;
|
|
147
|
+
return {
|
|
148
|
+
contract_version: COMPANY_OS_JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
149
|
+
status,
|
|
150
|
+
records,
|
|
151
|
+
coverage: {
|
|
152
|
+
complete,
|
|
153
|
+
storage: normalized.storage,
|
|
154
|
+
reason,
|
|
155
|
+
sources: [{ entrypoint: 'company_os', status, reason }],
|
|
156
|
+
total,
|
|
157
|
+
},
|
|
158
|
+
pagination: {
|
|
159
|
+
next_cursor: null,
|
|
160
|
+
previous_cursor: null,
|
|
161
|
+
limit: request.limit ?? null,
|
|
162
|
+
},
|
|
163
|
+
filters,
|
|
164
|
+
snapshot_id: normalized.snapshot_id,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function normalizeSourceResult(sourceResult) {
|
|
168
|
+
if (Array.isArray(sourceResult)) {
|
|
169
|
+
return {
|
|
170
|
+
status: 'available',
|
|
171
|
+
runs: sourceResult,
|
|
172
|
+
complete: true,
|
|
173
|
+
total: sourceResult.length,
|
|
174
|
+
storage: null,
|
|
175
|
+
reason: null,
|
|
176
|
+
snapshot_id: undefined,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// TypeScript does not remove a readonly array from this union with
|
|
180
|
+
// Array.isArray alone. The runtime check above has already established the
|
|
181
|
+
// structured branch, so keep that fact explicit for the property reads.
|
|
182
|
+
const structuredSourceResult = sourceResult;
|
|
183
|
+
const runs = Array.isArray(structuredSourceResult.runs) ? structuredSourceResult.runs : [];
|
|
184
|
+
const declaredStatus = structuredSourceResult.status ?? 'available';
|
|
185
|
+
const declaredComplete = structuredSourceResult.complete ?? (declaredStatus === 'available');
|
|
186
|
+
// A source cannot claim an available snapshot while also saying that its
|
|
187
|
+
// scan is incomplete. Normalize contradictory host output to partial so
|
|
188
|
+
// the common reader never advertises a complete count by accident.
|
|
189
|
+
const status = declaredStatus === 'unavailable'
|
|
190
|
+
? 'unavailable'
|
|
191
|
+
: declaredStatus === 'available' && declaredComplete ? 'available' : 'partial';
|
|
192
|
+
const complete = status === 'available' && declaredComplete;
|
|
193
|
+
const snapshotId = 'snapshot_id' in structuredSourceResult
|
|
194
|
+
&& isNonEmptyString(structuredSourceResult.snapshot_id)
|
|
195
|
+
? structuredSourceResult.snapshot_id
|
|
196
|
+
: undefined;
|
|
197
|
+
return {
|
|
198
|
+
status,
|
|
199
|
+
runs,
|
|
200
|
+
complete,
|
|
201
|
+
total: structuredSourceResult.total ?? null,
|
|
202
|
+
storage: structuredSourceResult.storage ?? null,
|
|
203
|
+
reason: structuredSourceResult.reason ?? null,
|
|
204
|
+
snapshot_id: snapshotId,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function projectSourceRun(sourceRun, access) {
|
|
208
|
+
if (!sameAccess(sourceRun.owner, access))
|
|
209
|
+
return {};
|
|
210
|
+
if (!isIsoTimestamp(sourceRun.recorded_at))
|
|
211
|
+
return {};
|
|
212
|
+
if (!sourceRun.view || sourceRun.view.run.status !== 'resolved' || !sourceRun.view.run.value)
|
|
213
|
+
return {};
|
|
214
|
+
const run = sourceRun.view.run.value;
|
|
215
|
+
const sourceId = sourceRun.source_id ?? run.runId;
|
|
216
|
+
if (!isSafeSourceIdentity(sourceId))
|
|
217
|
+
return {};
|
|
218
|
+
const missingFields = [];
|
|
219
|
+
const storedJudgment = sourceRun.public_judgment;
|
|
220
|
+
const judgmentStatus = readJudgmentStatus(storedJudgment?.status);
|
|
221
|
+
if (!readText(storedJudgment?.status))
|
|
222
|
+
missingFields.push('judgment.status');
|
|
223
|
+
const summary = readText(storedJudgment?.summary);
|
|
224
|
+
if (summary === null)
|
|
225
|
+
missingFields.push('judgment.summary');
|
|
226
|
+
const reason = readText(storedJudgment?.reason);
|
|
227
|
+
if (reason === null)
|
|
228
|
+
missingFields.push('judgment.reason');
|
|
229
|
+
const selectedReferences = projectSelectedReferences(sourceRun.view, storedJudgment, missingFields);
|
|
230
|
+
const alternatives = projectAlternatives(storedJudgment, missingFields);
|
|
231
|
+
const storedExecution = sourceRun.public_execution;
|
|
232
|
+
const executionStatus = readExecutionStatus(storedExecution?.status) ?? mapRunStatus(run.status);
|
|
233
|
+
const resultSummary = readText(storedExecution?.result_summary);
|
|
234
|
+
if (resultSummary === null)
|
|
235
|
+
missingFields.push('execution.result_summary');
|
|
236
|
+
// Completion is an operational fact. Without an explicitly stored
|
|
237
|
+
// semantic result, the outcome stays unconfirmed even when a separate
|
|
238
|
+
// historical resultEvaluation is present in the view.
|
|
239
|
+
const outcomeStatus = readOutcomeStatus(storedExecution?.outcome_status) ?? 'unconfirmed';
|
|
240
|
+
if (!readOutcomeStatus(storedExecution?.outcome_status)) {
|
|
241
|
+
missingFields.push('execution.outcome_status');
|
|
242
|
+
}
|
|
243
|
+
if (!hasOwn(sourceRun, 'project_code'))
|
|
244
|
+
missingFields.push('project_code');
|
|
245
|
+
if (!isNonEmptyString(sourceRun.turn_ref))
|
|
246
|
+
return {};
|
|
247
|
+
return {
|
|
248
|
+
record: {
|
|
249
|
+
schema_version: COMPANY_OS_JUDGMENT_HISTORY_RECORD_SCHEMA,
|
|
250
|
+
record_id: companyOsJudgmentHistoryRecordId(access, sourceId),
|
|
251
|
+
entrypoint: 'company_os',
|
|
252
|
+
recorded_at: sourceRun.recorded_at,
|
|
253
|
+
project_code: sourceRun.project_code ?? null,
|
|
254
|
+
turn_ref: sourceRun.turn_ref,
|
|
255
|
+
judgment: {
|
|
256
|
+
status: judgmentStatus,
|
|
257
|
+
summary,
|
|
258
|
+
reason,
|
|
259
|
+
selected_references: selectedReferences,
|
|
260
|
+
alternatives,
|
|
261
|
+
},
|
|
262
|
+
execution: {
|
|
263
|
+
status: executionStatus,
|
|
264
|
+
result_summary: resultSummary,
|
|
265
|
+
outcome_status: outcomeStatus,
|
|
266
|
+
},
|
|
267
|
+
missing_fields: unique(missingFields),
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function readJudgmentStatus(value) {
|
|
272
|
+
return value === 'resolved' || value === 'needs_clarification' ? value : 'unknown';
|
|
273
|
+
}
|
|
274
|
+
function projectSelectedReferences(view, storedJudgment, missingFields) {
|
|
275
|
+
if (storedJudgment && hasOwn(storedJudgment, 'selected_references')) {
|
|
276
|
+
if (storedJudgment.selected_references === null)
|
|
277
|
+
return null;
|
|
278
|
+
const refs = storedJudgment.selected_references;
|
|
279
|
+
if (!Array.isArray(refs)) {
|
|
280
|
+
missingFields.push('judgment.selected_references');
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const cloned = refs.map((reference) => cloneReference(reference));
|
|
284
|
+
if (cloned.some((reference) => reference === null)) {
|
|
285
|
+
missingFields.push('judgment.selected_references');
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return cloned;
|
|
289
|
+
}
|
|
290
|
+
// These references are already present in the historical view as exact
|
|
291
|
+
// versions. They are projected with their structural usage; no candidate
|
|
292
|
+
// search and no latest-version lookup happens here.
|
|
293
|
+
const references = referencesFromHistoricalView(view);
|
|
294
|
+
if (references.length === 0) {
|
|
295
|
+
if (!historicalReferenceSectionsAreKnownEmpty(view)) {
|
|
296
|
+
missingFields.push('judgment.selected_references');
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
return references;
|
|
302
|
+
}
|
|
303
|
+
function referencesFromHistoricalView(view) {
|
|
304
|
+
const references = [];
|
|
305
|
+
if (view.problem.status === 'resolved' && view.problem.value) {
|
|
306
|
+
for (const reference of view.problem.value.references) {
|
|
307
|
+
references.push({
|
|
308
|
+
ref: reference.id,
|
|
309
|
+
kind: reference.kind,
|
|
310
|
+
version: reference.revision,
|
|
311
|
+
digest: reference.digest,
|
|
312
|
+
why: null,
|
|
313
|
+
usage: 'problem_snapshot',
|
|
314
|
+
availability: 'recorded',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (view.objective.status === 'resolved' && view.objective.value) {
|
|
319
|
+
const reference = view.objective.value.ref;
|
|
320
|
+
references.push({
|
|
321
|
+
ref: reference.id,
|
|
322
|
+
kind: reference.type,
|
|
323
|
+
version: reference.revision,
|
|
324
|
+
digest: reference.digest,
|
|
325
|
+
why: null,
|
|
326
|
+
usage: 'objective',
|
|
327
|
+
availability: 'recorded',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (view.evidence.status === 'resolved' && view.evidence.items) {
|
|
331
|
+
for (const evidence of view.evidence.items) {
|
|
332
|
+
references.push({
|
|
333
|
+
ref: evidence.id,
|
|
334
|
+
kind: evidence.kind,
|
|
335
|
+
version: evidence.revision,
|
|
336
|
+
digest: evidence.digest ?? null,
|
|
337
|
+
why: null,
|
|
338
|
+
usage: 'evidence',
|
|
339
|
+
availability: 'recorded',
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return dedupeReferences(references);
|
|
344
|
+
}
|
|
345
|
+
function historicalReferenceSectionsAreKnownEmpty(view) {
|
|
346
|
+
const problemKnown = view.problem.status === 'resolved' && Boolean(view.problem.value);
|
|
347
|
+
const objectiveKnown = view.objective.status === 'resolved' && Boolean(view.objective.value);
|
|
348
|
+
const evidenceKnown = view.evidence.status === 'resolved' && view.evidence.items !== null && view.evidence.absence_confirmed;
|
|
349
|
+
// This helper is called only after the historical projection found no
|
|
350
|
+
// references. An empty list is safe only when every reference-bearing
|
|
351
|
+
// section was resolved; one resolved section cannot establish that an
|
|
352
|
+
// unknown section had no references.
|
|
353
|
+
return problemKnown && objectiveKnown && evidenceKnown;
|
|
354
|
+
}
|
|
355
|
+
function projectAlternatives(storedJudgment, missingFields) {
|
|
356
|
+
if (!storedJudgment || !hasOwn(storedJudgment, 'alternatives')) {
|
|
357
|
+
missingFields.push('judgment.alternatives');
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
if (storedJudgment.alternatives === null)
|
|
361
|
+
return null;
|
|
362
|
+
if (!Array.isArray(storedJudgment.alternatives)) {
|
|
363
|
+
missingFields.push('judgment.alternatives');
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const projected = storedJudgment.alternatives.map((alternative) => {
|
|
367
|
+
if (!alternative || !isNonEmptyString(alternative.label))
|
|
368
|
+
return null;
|
|
369
|
+
return {
|
|
370
|
+
label: alternative.label,
|
|
371
|
+
evaluation: readNullableText(alternative.evaluation),
|
|
372
|
+
adopted: typeof alternative.adopted === 'boolean' ? alternative.adopted : null,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
if (projected.some((alternative) => alternative === null)) {
|
|
376
|
+
missingFields.push('judgment.alternatives');
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
return projected;
|
|
380
|
+
}
|
|
381
|
+
function cloneReference(reference) {
|
|
382
|
+
if (!isValidReference(reference))
|
|
383
|
+
return null;
|
|
384
|
+
return {
|
|
385
|
+
ref: reference.ref,
|
|
386
|
+
kind: reference.kind,
|
|
387
|
+
version: reference.version,
|
|
388
|
+
digest: reference.digest,
|
|
389
|
+
why: reference.why,
|
|
390
|
+
usage: reference.usage,
|
|
391
|
+
availability: reference.availability,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function dedupeReferences(references) {
|
|
395
|
+
const seen = new Set();
|
|
396
|
+
return references.filter((reference) => {
|
|
397
|
+
const key = [reference.ref, reference.kind, reference.version, reference.digest, reference.usage].join('\u0000');
|
|
398
|
+
if (seen.has(key))
|
|
399
|
+
return false;
|
|
400
|
+
seen.add(key);
|
|
401
|
+
return true;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
function mapRunStatus(status) {
|
|
405
|
+
return isExecutionStatus(status) ? status : 'unknown';
|
|
406
|
+
}
|
|
407
|
+
function readExecutionStatus(value) {
|
|
408
|
+
return isExecutionStatus(value) ? value : null;
|
|
409
|
+
}
|
|
410
|
+
function readOutcomeStatus(value) {
|
|
411
|
+
return isOutcomeStatus(value) ? value : null;
|
|
412
|
+
}
|
|
413
|
+
function isExecutionStatus(value) {
|
|
414
|
+
return typeof value === 'string' && EXECUTION_STATUSES.includes(value);
|
|
415
|
+
}
|
|
416
|
+
function isOutcomeStatus(value) {
|
|
417
|
+
return typeof value === 'string' && OUTCOME_STATUSES.includes(value);
|
|
418
|
+
}
|
|
419
|
+
function readText(value) {
|
|
420
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
421
|
+
}
|
|
422
|
+
function readNullableText(value) {
|
|
423
|
+
return value === null ? null : readText(value);
|
|
424
|
+
}
|
|
425
|
+
function isValidReference(value) {
|
|
426
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
427
|
+
return false;
|
|
428
|
+
const reference = value;
|
|
429
|
+
return isNonEmptyString(reference.ref)
|
|
430
|
+
&& isNullableText(reference.kind)
|
|
431
|
+
&& isNullableText(reference.version)
|
|
432
|
+
&& isNullableText(reference.digest)
|
|
433
|
+
&& isNullableText(reference.why)
|
|
434
|
+
&& isNullableText(reference.usage)
|
|
435
|
+
&& (reference.availability === 'recorded'
|
|
436
|
+
|| reference.availability === 'permission_denied'
|
|
437
|
+
|| reference.availability === 'unavailable'
|
|
438
|
+
|| reference.availability === 'unknown');
|
|
439
|
+
}
|
|
440
|
+
function isNullableText(value) {
|
|
441
|
+
return value === null || isNonEmptyString(value);
|
|
442
|
+
}
|
|
443
|
+
function sameAccess(owner, access) {
|
|
444
|
+
return owner.tenantId === access.tenantId
|
|
445
|
+
&& owner.principal === access.principal
|
|
446
|
+
&& owner.scopeId === access.scopeId;
|
|
447
|
+
}
|
|
448
|
+
function assertAccess(access) {
|
|
449
|
+
if (!access || !isNonEmptyString(access.tenantId) || !isNonEmptyString(access.principal) || !isNonEmptyString(access.scopeId)) {
|
|
450
|
+
throw new TypeError('Company OS judgment history requires trusted tenant, principal, and scope');
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function isIsoTimestamp(value) {
|
|
454
|
+
return typeof value === 'string' && value.trim().length > 0 && Number.isFinite(Date.parse(value));
|
|
455
|
+
}
|
|
456
|
+
function isNonEmptyString(value) {
|
|
457
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
458
|
+
}
|
|
459
|
+
function isSafeSourceIdentity(value) {
|
|
460
|
+
return isNonEmptyString(value)
|
|
461
|
+
&& value.length <= 256
|
|
462
|
+
&& !/[\u0000-\u001f\u007f]/u.test(value);
|
|
463
|
+
}
|
|
464
|
+
function hasOwn(value, key) {
|
|
465
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
466
|
+
}
|
|
467
|
+
function unique(values) {
|
|
468
|
+
return [...new Set(values)];
|
|
469
|
+
}
|
|
470
|
+
function unavailableList(filters, reason, options = {}) {
|
|
471
|
+
return {
|
|
472
|
+
contract_version: COMPANY_OS_JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
473
|
+
status: 'unavailable',
|
|
474
|
+
records: [],
|
|
475
|
+
coverage: {
|
|
476
|
+
complete: false,
|
|
477
|
+
storage: options.storage ?? null,
|
|
478
|
+
reason,
|
|
479
|
+
sources: [{ entrypoint: 'company_os', status: 'unavailable', reason }],
|
|
480
|
+
total: null,
|
|
481
|
+
},
|
|
482
|
+
pagination: {
|
|
483
|
+
next_cursor: null,
|
|
484
|
+
previous_cursor: null,
|
|
485
|
+
limit: null,
|
|
486
|
+
},
|
|
487
|
+
filters,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { createJudgmentHistoryReader, type JudgmentHistoryFeedbackWriter, type JudgmentHistoryReader } from './judgment-history.js';
|
|
3
|
+
export declare const JUDGMENT_HISTORY_HTTP_VERSION: "judgment-history-http.v1";
|
|
4
|
+
export declare const JUDGMENT_HISTORY_HTTP_PREFIX: "/api/judgment-history";
|
|
5
|
+
export declare const JUDGMENT_HISTORY_FEEDBACK_BODY_LIMIT_BYTES: number;
|
|
6
|
+
export interface JudgmentHistoryHttpOptions {
|
|
7
|
+
readonly reader: JudgmentHistoryReader;
|
|
8
|
+
readonly basePath?: string;
|
|
9
|
+
/** Host-owned authorization callback. Browser input never selects its owner. */
|
|
10
|
+
readonly assertWriteAllowed?: (request: IncomingMessage) => void | Promise<void>;
|
|
11
|
+
/** Append-only writer; GET-only compositions can omit this seam. */
|
|
12
|
+
readonly writeFeedback?: JudgmentHistoryFeedbackWriter;
|
|
13
|
+
}
|
|
14
|
+
export type JudgmentHistoryHttpHandler = (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
|
|
15
|
+
/** Node-style GET route for the common normal-judgment history contract. */
|
|
16
|
+
export declare function createJudgmentHistoryHttpHandler(options: JudgmentHistoryHttpOptions): JudgmentHistoryHttpHandler;
|
|
17
|
+
/** Convenience composition seam for adapters that want the HTTP contract without a local journal. */
|
|
18
|
+
export declare function createJudgmentHistoryHttpReader(options: Parameters<typeof createJudgmentHistoryReader>[0]): JudgmentHistoryReader;
|