@knpkv/herdr-work 0.6.0 → 0.7.1
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/README.md +18 -1
- package/dist/internal/terminal-reconcile.d.ts +5 -3
- package/dist/internal/terminal-reconcile.d.ts.map +1 -1
- package/dist/internal/terminal-reconcile.js +12 -2
- package/dist/internal/terminal-reconcile.js.map +1 -1
- package/dist/model.d.ts +31 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +23 -3
- package/dist/model.js.map +1 -1
- package/dist/service.d.ts +3 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +4 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +96 -15
- package/dist/store.js.map +1 -1
- package/package.json +3 -3
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAOA,OAAO,EAAS,MAAM,EAAE,MAAM,EAAS,MAAM,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAA;AAI7E,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,6BAA6B,EAC7B,2BAA2B,EAC3B,mCAAmC,EACnC,kCAAkC,EAClC,gCAAgC,EAChC,iCAAiC,EACjC,gCAAgC,EAChC,gCAAgC,EAChC,iCAAiC,EACjC,uBAAuB,EACvB,0BAA0B,EAC1B,iCAAiC,EACjC,6BAA6B,EAC7B,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,EAC9B,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,4BAA4B,EAC7B,MAAM,aAAa,CAAA;AAEpB,OAAO,EAIL,oCAAoC,EAGrC,MAAM,uCAAuC,CAAA;AAmD9C,OAAO,EAGL,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EAEvB,mBAAmB,EAInB,iBAAiB,EAEjB,kBAAkB,EAElB,kBAAkB,EAGlB,aAAa,EACb,eAAe,EACf,uBAAuB,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/store.js
CHANGED
|
@@ -13,7 +13,7 @@ import { workHistoryError } from "./internal/history-validation.js";
|
|
|
13
13
|
import { LaneOperationLedgerRow, LaneOperationTotalsRow, planLegacyLaneOperations, resolveLegacyLaneClaim } from "./internal/legacy-lane-claim.js";
|
|
14
14
|
import { enforcesOneHandoffPerSession, SessionIndexDefinition, sessionIndexDefinitionQuery } from "./internal/session-index.js";
|
|
15
15
|
import { goalActivityLimit, terminalCandidates, terminalCheckpoint, withoutBlockers } from "./internal/terminal-reconcile.js";
|
|
16
|
-
import { admissionEvidence, isTerminalWorkState, WorkAdmissionTarget, WorkAgentBinding, WorkAgentBindingRequest, WorkCoordinatorSessionId, WorkDecisionHandoff, WorkDispatchHandoff, WorkExistingGoalRecovery, WorkGoal, WorkGoalAbandoned, WorkGoalAbandonment, WorkGoalCheckpoint, WorkGoalId, WorkGoalReassigned, WorkGoalReassignment, workHistoryMaxEvents, WorkLaneClaim, WorkLaneClaimed, WorkObservationEnvelope, workObservationMaxSkewMillis, WorkObservationSubject, WorkObservedAdmission, WorkObservedFact, workObservedFactMaxBytes, workObservedFactMaxRecords, WorkObservedFailure, WorkProspectiveAdmission, WorkPullRequestLink, workReconcilerHeadroom, WorkRecoveryTarget, workSnapshotMaxGoals } from "./model.js";
|
|
16
|
+
import { admissionEvidence, isTerminalWorkState, WorkAdmissionTarget, WorkAgentBinding, WorkAgentBindingRequest, WorkCoordinatorSessionId, WorkDecisionHandoff, WorkDispatchHandoff, WorkExistingGoalRecovery, WorkGoal, WorkGoalAbandoned, WorkGoalAbandonment, WorkGoalCheckpoint, WorkGoalId, WorkGoalReassigned, WorkGoalReassignment, workHistoryMaxEvents, WorkLaneClaim, WorkLaneClaimed, WorkObservationEnvelope, WorkObservationId, workObservationMaxSkewMillis, WorkObservationSubject, WorkObservedAdmission, WorkObservedFact, workObservedFactMaxBytes, workObservedFactMaxRecords, WorkObservedFailure, WorkProspectiveAdmission, WorkPullRequestLink, WorkReconcileOptions, workReconcilerHeadroom, WorkRecoveryTarget, workSnapshotMaxGoals } from "./model.js";
|
|
17
17
|
import { asciiLower, canonicalSubject, observationSubject, reconcilerEventPrefix } from "./observed.js";
|
|
18
18
|
const StoredEventRow = Schema.Struct({ record: Schema.String });
|
|
19
19
|
const StoredEventRows = Schema.Array(StoredEventRow);
|
|
@@ -870,7 +870,8 @@ const readDecisionLedgerTotals = (database) => Schema.decodeUnknownSync(Decision
|
|
|
870
870
|
FROM work_decision_totals WHERE singleton = 1`).get());
|
|
871
871
|
const readLaneOperationLedgerTotals = (database) => Schema.decodeUnknownSync(LaneOperationLedgerTotalsRow)(database.prepare(`SELECT operation_count AS operationCount, operation_bytes AS operationBytes
|
|
872
872
|
FROM work_lane_operation_totals WHERE singleton = 1`).get());
|
|
873
|
-
const ObservationIdRow = Schema.Struct({ observationId: Schema.String });
|
|
873
|
+
const ObservationIdRow = Schema.Struct({ observationId: Schema.String, confirmedAt: Schema.Number });
|
|
874
|
+
const FailureLastAtRow = Schema.Struct({ lastAt: Schema.Number });
|
|
874
875
|
const ReassignmentRow = Schema.Struct({ approvalJobId: Schema.String, goalId: Schema.String, record: Schema.String });
|
|
875
876
|
/** Applies a reassignment's agent choice; `keep` is only reached when the goal has no target. */
|
|
876
877
|
const withReassignedAgent = (goal, toAgent) => {
|
|
@@ -1273,7 +1274,8 @@ const ObservedFactRow = Schema.Struct({
|
|
|
1273
1274
|
});
|
|
1274
1275
|
const StoredFactRow = Schema.Struct({ observationId: Schema.String, confirmedAt: Schema.Number });
|
|
1275
1276
|
const FactTotalsRow = Schema.Struct({ count: Schema.Number, bytes: Schema.Number });
|
|
1276
|
-
const EvictionRow = Schema.Struct({ subject: Schema.String, bytes: Schema.Number });
|
|
1277
|
+
const EvictionRow = Schema.Struct({ subject: Schema.String, bytes: Schema.Number, age: Schema.Number });
|
|
1278
|
+
const EvictedThroughRow = Schema.Struct({ evictedThrough: Schema.Number });
|
|
1277
1279
|
const canonicalObservation = (observation) => observation._tag === "agent"
|
|
1278
1280
|
? { ...observation, host: asciiLower(observation.host) }
|
|
1279
1281
|
: { ...observation, repository: asciiLower(observation.repository) };
|
|
@@ -1292,6 +1294,13 @@ const writeObservations = (database, prepared) => {
|
|
|
1292
1294
|
try {
|
|
1293
1295
|
const readFact = database.prepare(`SELECT observation_id AS observationId, confirmed_at AS confirmedAt
|
|
1294
1296
|
FROM work_observed_facts WHERE subject = ?`);
|
|
1297
|
+
// The newest time of any row eviction has removed. A subject with no row
|
|
1298
|
+
// may have had one, so a fact read no newer than this can't be shown to
|
|
1299
|
+
// be current, and is stale: eviction never makes an old read new again.
|
|
1300
|
+
const evictedRow = database.prepare("SELECT evicted_through AS evictedThrough FROM work_observed_eviction WHERE singleton = 1").get();
|
|
1301
|
+
const evictedThrough = evictedRow === undefined
|
|
1302
|
+
? Number.NEGATIVE_INFINITY
|
|
1303
|
+
: Schema.decodeUnknownSync(EvictedThroughRow)(evictedRow).evictedThrough;
|
|
1295
1304
|
const upsertFact = database.prepare(`INSERT INTO work_observed_facts (subject, observation_id, observed_at, confirmed_at, record)
|
|
1296
1305
|
VALUES (?, ?, ?, ?, ?)
|
|
1297
1306
|
ON CONFLICT (subject) DO UPDATE SET observation_id = excluded.observation_id,
|
|
@@ -1329,28 +1338,42 @@ const writeObservations = (database, prepared) => {
|
|
|
1329
1338
|
recordFailure.run(item.subject, item.source, item.reason, item.observedAt, item.observedAt);
|
|
1330
1339
|
return { _tag: "unknown", reason: item.reason, subject: item.subject };
|
|
1331
1340
|
}
|
|
1341
|
+
if (stored === undefined && item.observedAt <= evictedThrough)
|
|
1342
|
+
return { _tag: "stale", subject: item.subject };
|
|
1343
|
+
// The same facts read before the stored confirmation confirm nothing now.
|
|
1344
|
+
if (stored?.observationId === item.observationId && stored.confirmedAt > item.observedAt) {
|
|
1345
|
+
return { _tag: "stale", subject: item.subject };
|
|
1346
|
+
}
|
|
1332
1347
|
if (stored?.observationId === item.observationId) {
|
|
1333
1348
|
confirmFact.run(item.observedAt, item.subject);
|
|
1334
1349
|
endFailures.run(item.subject, item.observedAt);
|
|
1335
|
-
return { _tag: "unchanged", subject: item.subject };
|
|
1350
|
+
return { _tag: "unchanged", observationId: item.observationId, subject: item.subject };
|
|
1336
1351
|
}
|
|
1337
1352
|
if (stored !== undefined && stored.confirmedAt >= item.observedAt)
|
|
1338
1353
|
return { _tag: "stale", subject: item.subject };
|
|
1339
1354
|
upsertFact.run(item.subject, item.observationId, item.observedAt, item.observedAt, item.record);
|
|
1340
1355
|
endFailures.run(item.subject, item.observedAt);
|
|
1341
|
-
return { _tag: "stored", subject: item.subject };
|
|
1356
|
+
return { _tag: "stored", observationId: item.observationId, subject: item.subject };
|
|
1342
1357
|
});
|
|
1343
1358
|
// Totals are read once and kept current as rows go, so eviction is one
|
|
1344
1359
|
// indexed lookup and delete per row, not a full rescan per row.
|
|
1345
|
-
|
|
1360
|
+
// A failure disputes its subject's fact while it is newer than that fact's
|
|
1361
|
+
// confirmation; evicting such a failure evicts the fact with it, so a
|
|
1362
|
+
// disputed fact never outlives the record of its dispute.
|
|
1363
|
+
const removeDisputedFact = database.prepare(`DELETE FROM work_observed_facts WHERE subject = ? AND confirmed_at <
|
|
1364
|
+
(SELECT last_at FROM work_observed_failures WHERE subject = ?)`);
|
|
1365
|
+
let newestEvicted = Number.NEGATIVE_INFINITY;
|
|
1366
|
+
const evict = (table, age, payload, beforeRemove) => {
|
|
1346
1367
|
const size = `length(CAST(subject AS BLOB)) + length(CAST(${payload} AS BLOB))`;
|
|
1347
1368
|
const totals = Schema.decodeUnknownSync(FactTotalsRow)(database.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(${size}), 0) AS bytes FROM ${table}`).get());
|
|
1348
|
-
const oldest = database.prepare(`SELECT subject, ${size} AS bytes FROM ${table} ORDER BY ${age} ASC, subject ASC LIMIT 1`);
|
|
1369
|
+
const oldest = database.prepare(`SELECT subject, ${size} AS bytes, ${age} AS age FROM ${table} ORDER BY ${age} ASC, subject ASC LIMIT 1`);
|
|
1349
1370
|
const remove = database.prepare(`DELETE FROM ${table} WHERE subject = ?`);
|
|
1350
1371
|
let { bytes, count } = totals;
|
|
1351
1372
|
let evicted = 0;
|
|
1352
1373
|
while (count > workObservedFactMaxRecords || bytes > workObservedFactMaxBytes) {
|
|
1353
1374
|
const row = Schema.decodeUnknownSync(EvictionRow)(oldest.get());
|
|
1375
|
+
newestEvicted = Math.max(newestEvicted, row.age);
|
|
1376
|
+
evicted += beforeRemove?.(row.subject) ?? 0;
|
|
1354
1377
|
remove.run(row.subject);
|
|
1355
1378
|
count -= 1;
|
|
1356
1379
|
bytes -= row.bytes;
|
|
@@ -1361,9 +1384,32 @@ const writeObservations = (database, prepared) => {
|
|
|
1361
1384
|
// The least recently read rows go first: a fact by its last confirmation,
|
|
1362
1385
|
// a failure by its latest failed read.
|
|
1363
1386
|
const evicted = evict("work_observed_facts", "confirmed_at", "record") +
|
|
1364
|
-
evict("work_observed_failures", "last_at", "reason");
|
|
1387
|
+
evict("work_observed_failures", "last_at", "reason", (subject) => Number(removeDisputedFact.run(subject, subject).changes));
|
|
1388
|
+
if (newestEvicted > evictedThrough) {
|
|
1389
|
+
database.prepare(`INSERT INTO work_observed_eviction (singleton, evicted_through) VALUES (1, ?)
|
|
1390
|
+
ON CONFLICT (singleton) DO UPDATE SET evicted_through = max(evicted_through, excluded.evicted_through)`).run(newestEvicted);
|
|
1391
|
+
}
|
|
1392
|
+
// Reported last, after every write and eviction in this call: a stored or
|
|
1393
|
+
// unchanged fact that a later envelope replaced, or eviction removed, is no
|
|
1394
|
+
// longer the subject's fact and confirms nothing.
|
|
1395
|
+
// Only a subject's last accepted read can still be its fact: an earlier one
|
|
1396
|
+
// with the same content (A, B, A) was replaced in between.
|
|
1397
|
+
const lastAccepted = new Map();
|
|
1398
|
+
outcomes.forEach((outcome, index) => {
|
|
1399
|
+
if (outcome._tag === "stored" || outcome._tag === "unchanged")
|
|
1400
|
+
lastAccepted.set(outcome.subject, index);
|
|
1401
|
+
});
|
|
1402
|
+
const finalOutcomes = outcomes.map((outcome, index) => {
|
|
1403
|
+
if (outcome._tag !== "stored" && outcome._tag !== "unchanged")
|
|
1404
|
+
return outcome;
|
|
1405
|
+
if (lastAccepted.get(outcome.subject) !== index)
|
|
1406
|
+
return { _tag: "stale", subject: outcome.subject };
|
|
1407
|
+
const row = readFact.get(outcome.subject);
|
|
1408
|
+
const current = row === undefined ? undefined : Schema.decodeUnknownSync(StoredFactRow)(row);
|
|
1409
|
+
return current?.observationId === outcome.observationId ? outcome : { _tag: "stale", subject: outcome.subject };
|
|
1410
|
+
});
|
|
1365
1411
|
database.exec("COMMIT");
|
|
1366
|
-
return { evicted, outcomes };
|
|
1412
|
+
return { evicted, outcomes: finalOutcomes };
|
|
1367
1413
|
}
|
|
1368
1414
|
catch (cause) {
|
|
1369
1415
|
if (database.isTransaction)
|
|
@@ -1419,6 +1465,7 @@ export class WorkStore {
|
|
|
1419
1465
|
try {
|
|
1420
1466
|
migrateLegacyAuthorityTables(database);
|
|
1421
1467
|
const hadLaneOperationLedger = database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'work_lane_operations'").get() !== undefined;
|
|
1468
|
+
const hadEvictionWatermark = database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'work_observed_eviction'").get() !== undefined;
|
|
1422
1469
|
database.exec(`
|
|
1423
1470
|
CREATE TABLE IF NOT EXISTS work_goal_events (
|
|
1424
1471
|
event_id TEXT PRIMARY KEY,
|
|
@@ -1553,12 +1600,31 @@ export class WorkStore {
|
|
|
1553
1600
|
);
|
|
1554
1601
|
CREATE INDEX IF NOT EXISTS work_observed_failures_age
|
|
1555
1602
|
ON work_observed_failures (last_at, subject);
|
|
1603
|
+
CREATE TABLE IF NOT EXISTS work_observed_eviction (
|
|
1604
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
1605
|
+
evicted_through INTEGER NOT NULL
|
|
1606
|
+
);
|
|
1556
1607
|
`);
|
|
1557
1608
|
requireUniqueSessionIndex(database);
|
|
1558
1609
|
const columns = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare("PRAGMA table_info(work_goal_events)").all());
|
|
1559
1610
|
if (!columns.some(({ name }) => name === "transaction_id")) {
|
|
1560
1611
|
database.exec("ALTER TABLE work_goal_events ADD COLUMN transaction_id TEXT");
|
|
1561
1612
|
}
|
|
1613
|
+
if (!hadEvictionWatermark) {
|
|
1614
|
+
// Older stores evicted without a watermark, and may have kept a fact
|
|
1615
|
+
// whose dispute they evicted. Their reads are a cache of the
|
|
1616
|
+
// providers, so they are dropped and every one becomes stale; the
|
|
1617
|
+
// next pass reads them again, newer.
|
|
1618
|
+
database.exec(`
|
|
1619
|
+
INSERT INTO work_observed_eviction (singleton, evicted_through)
|
|
1620
|
+
SELECT 1, max(at) FROM (
|
|
1621
|
+
SELECT confirmed_at AS at FROM work_observed_facts
|
|
1622
|
+
UNION ALL SELECT last_at FROM work_observed_failures
|
|
1623
|
+
) HAVING max(at) IS NOT NULL;
|
|
1624
|
+
DELETE FROM work_observed_facts;
|
|
1625
|
+
DELETE FROM work_observed_failures;
|
|
1626
|
+
`);
|
|
1627
|
+
}
|
|
1562
1628
|
if (!hadLaneOperationLedger) {
|
|
1563
1629
|
database.exec(`
|
|
1564
1630
|
INSERT OR IGNORE INTO work_lane_operations
|
|
@@ -2783,9 +2849,16 @@ export class WorkStore {
|
|
|
2783
2849
|
const headRow = this.#database.prepare(`SELECT record FROM work_goal_events WHERE goal_id = ?
|
|
2784
2850
|
ORDER BY occurred_at DESC, event_id DESC LIMIT 1`).get(decoded.goal.id);
|
|
2785
2851
|
const head = headRow === undefined ? null : Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(Schema.decodeUnknownSync(StoredEventRow)(headRow).record));
|
|
2786
|
-
const factRow = this.#database.prepare(
|
|
2852
|
+
const factRow = this.#database.prepare(`SELECT observation_id AS observationId, confirmed_at AS confirmedAt
|
|
2853
|
+
FROM work_observed_facts WHERE subject = ?`).get(guard.fact.subject);
|
|
2787
2854
|
const fact = factRow === undefined ? null : Schema.decodeUnknownSync(ObservationIdRow)(factRow);
|
|
2788
|
-
|
|
2855
|
+
const failureRow = this.#database.prepare("SELECT last_at AS lastAt FROM work_observed_failures WHERE subject = ?").get(guard.fact.subject);
|
|
2856
|
+
const failedAt = failureRow === undefined
|
|
2857
|
+
? null
|
|
2858
|
+
: Schema.decodeUnknownSync(FailureLastAtRow)(failureRow).lastAt;
|
|
2859
|
+
// A failed read recorded while the plan was made also makes it stale.
|
|
2860
|
+
const stillFresh = fact !== null && (failedAt === null || failedAt <= fact.confirmedAt);
|
|
2861
|
+
if (head?.eventId !== guard.head.eventId || fact?.observationId !== guard.fact.observationId || !stillFresh) {
|
|
2789
2862
|
return reject(new WorkGoalRevisionConflictError({
|
|
2790
2863
|
actualEventId: head?.eventId ?? null,
|
|
2791
2864
|
actualUpdatedAt: head?.goal.updatedAt ?? null,
|
|
@@ -3968,7 +4041,8 @@ export class WorkStore {
|
|
|
3968
4041
|
reconcilerEvents
|
|
3969
4042
|
};
|
|
3970
4043
|
});
|
|
3971
|
-
reconcile = Effect.fn("WorkStore.reconcile")(function* () {
|
|
4044
|
+
reconcile = Effect.fn("WorkStore.reconcile")(function* (request) {
|
|
4045
|
+
const options = yield* Schema.decodeUnknownEffect(WorkReconcileOptions)(request).pipe(Effect.mapError(storeError("reconcile.options")));
|
|
3972
4046
|
const source = yield* this.snapshotInput();
|
|
3973
4047
|
if (workHistoryMaxEvents - source.events.length < workReconcilerHeadroom) {
|
|
3974
4048
|
return yield* new WorkProjectionError({
|
|
@@ -3977,13 +4051,20 @@ export class WorkStore {
|
|
|
3977
4051
|
reason: "capacity_exceeded"
|
|
3978
4052
|
});
|
|
3979
4053
|
}
|
|
4054
|
+
// A confirmation names the fact the caller read; one that is no longer the
|
|
4055
|
+
// subject's stored fact (replaced, evicted) is the caller's stale view.
|
|
4056
|
+
const current = new Map(source.facts.map((fact) => [fact.subject, fact.observationId]));
|
|
4057
|
+
const outdated = options.confirmed.filter(({ observationId, subject }) => current.get(subject) !== observationId);
|
|
4058
|
+
if (outdated.length > 0) {
|
|
4059
|
+
return yield* new WorkStoreError({ cause: outdated, operation: "reconcile.confirmed" });
|
|
4060
|
+
}
|
|
3980
4061
|
// A goal is stamped by the reconciler at most once, whatever its facts
|
|
3981
4062
|
// look like later: a reopened goal stays the owner's.
|
|
3982
4063
|
const stamped = new Map(source.reconcilerEvents.map(({ eventId, goalId }) => [goalId, eventId]));
|
|
3983
4064
|
const cryptoService = this.#cryptoService;
|
|
3984
4065
|
const now = yield* Clock.currentTimeMillis;
|
|
3985
4066
|
const appendAt = (checkpoint, guard) => this.appendAt(checkpoint, guard);
|
|
3986
|
-
return yield* Effect.forEach(terminalCandidates(source.events, source.facts), Effect.fnUntraced(function* (candidate) {
|
|
4067
|
+
return yield* Effect.forEach(terminalCandidates(source.events, source.facts, source.failures, new Set(options.confirmed.map(({ observationId, subject }) => `${subject}\u0000${observationId}`))), Effect.fnUntraced(function* (candidate) {
|
|
3987
4068
|
const goalId = candidate.head.goal.id;
|
|
3988
4069
|
const previous = stamped.get(goalId);
|
|
3989
4070
|
if (previous !== undefined)
|
|
@@ -4042,10 +4123,10 @@ export class WorkStore {
|
|
|
4042
4123
|
observation: canonicalObservation(envelope.observation)
|
|
4043
4124
|
})
|
|
4044
4125
|
.observation);
|
|
4045
|
-
const
|
|
4126
|
+
const observationId = yield* cryptoService.digest("SHA-256", utf8.encode(record)).pipe(Effect.map(Hex.encode), Effect.flatMap(Schema.decodeUnknownEffect(WorkObservationId)), Effect.mapError(storeError("observe.digest")));
|
|
4046
4127
|
return {
|
|
4047
4128
|
_tag: "fact",
|
|
4048
|
-
observationId
|
|
4129
|
+
observationId,
|
|
4049
4130
|
observedAt: Math.min(envelope.observedAt, clockNow),
|
|
4050
4131
|
record,
|
|
4051
4132
|
subject
|