mirror of
https://github.com/morluto/rea.git
synced 2026-11-01 04:16:28 +00:00
Share clock rendering between analysis and local serving. Preserve exact sub-millisecond facts, bound learned format state per field, and charge retained output to the projection budget. Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
418 lines
14 KiB
TypeScript
418 lines
14 KiB
TypeScript
import { z } from "zod";
|
|
import type { AnalysisError } from "../analysisErrorBase.js";
|
|
import { jsonValueSchema } from "../jsonValue.js";
|
|
import { ok, type Result } from "../result.js";
|
|
import type {
|
|
AnalysisViewParent,
|
|
AnalysisViewRequest,
|
|
UnsignedAnalysisView,
|
|
} from "./analysisView.js";
|
|
import { EnvironmentStateAccumulator } from "./EnvironmentStateAccumulator.js";
|
|
import {
|
|
groupCaptureRecords,
|
|
templateRulesSchema,
|
|
} from "./environmentManifestView.js";
|
|
|
|
export const environmentStateViewRequestSchema = z.strictObject({
|
|
kind: z.literal("environment-state"),
|
|
template_rules: templateRulesSchema.describe(
|
|
"Opt-in template rules as for environment-manifest, each listed at most once. Use the rules of the manifest the state accompanies so tool names match.",
|
|
),
|
|
});
|
|
|
|
const jsonObjectFieldsSchema = z.record(z.string(), jsonValueSchema);
|
|
const queryParameterSchema = z.discriminatedUnion("role", [
|
|
z.strictObject({
|
|
role: z.literal("filter"),
|
|
field: z
|
|
.string()
|
|
.describe(
|
|
"Entities whose field equals the value as text, or whose array field holds it directly or as an object's id, slug or name.",
|
|
),
|
|
}),
|
|
z.strictObject({
|
|
role: z.literal("search"),
|
|
fields: z
|
|
.array(z.string())
|
|
.describe(
|
|
"Entities with a string field containing a plain substring value, ignoring case; an empty value matches every entity. Syntax-like operators replay instead.",
|
|
),
|
|
}),
|
|
z
|
|
.strictObject({ role: z.literal("sort") })
|
|
.describe(
|
|
"Orders by the field the value names (`name`, `-name`, `name:desc`, `name-ASC`): numbers by value, text ignoring case, null last ascending.",
|
|
),
|
|
z
|
|
.strictObject({ role: z.literal("direction") })
|
|
.describe("asc or desc for the sort parameter."),
|
|
z
|
|
.strictObject({ role: z.literal("limit") })
|
|
.describe("Page size; zero or a negative value lists every match."),
|
|
z
|
|
.strictObject({ role: z.literal("offset") })
|
|
.describe("Matches skipped before the page."),
|
|
z.strictObject({
|
|
role: z.literal("page"),
|
|
first: z
|
|
.number()
|
|
.int()
|
|
.describe(
|
|
"The first page, 0 or 1, when recordings distinguish the convention.",
|
|
),
|
|
}),
|
|
z.strictObject({
|
|
role: z.literal("inert"),
|
|
values: z
|
|
.array(z.string())
|
|
.describe(
|
|
"Recorded values that change nothing the view lists; other values are not computed.",
|
|
),
|
|
}),
|
|
]);
|
|
const listQuerySchema = z.strictObject({
|
|
parameters: z
|
|
.record(z.string(), queryParameterSchema)
|
|
.describe(
|
|
"What each query parameter does. A call whose every parameter has a role is answered by evaluating them over the state.",
|
|
),
|
|
record_ordinals: z
|
|
.array(z.number().int().nonnegative())
|
|
.describe("Checked lines the query reproduces, answered by evaluating it."),
|
|
totals: z
|
|
.array(z.string())
|
|
.describe(
|
|
"Observed envelope members with reproducible counts of every match before paging; absent or unknown representations stay recorded.",
|
|
),
|
|
});
|
|
const checkedSchema = z
|
|
.array(z.number().int().nonnegative())
|
|
.describe(
|
|
"Latest init record per request line that the view reproduced from the state.",
|
|
);
|
|
const clockFormatSchema = z.discriminatedUnion("kind", [
|
|
z.strictObject({
|
|
kind: z.literal("iso"),
|
|
separator: z.enum(["T", " "]),
|
|
fraction: z.number().int().nonnegative().max(9),
|
|
zone: z.string(),
|
|
}),
|
|
z.strictObject({ kind: z.literal("epoch"), unit: z.enum(["s", "ms"]) }),
|
|
]);
|
|
const entityViewFields = {
|
|
tool: z.string(),
|
|
type: z.string(),
|
|
pointer: z
|
|
.string()
|
|
.describe(
|
|
"JSON pointer to the entity object or array within the response: the root or one member, or for an item the element of a one-element array there (/0 or /<member>/0).",
|
|
),
|
|
fields: z
|
|
.array(z.string())
|
|
.describe("Ordered fields the view shows of each entity."),
|
|
required_fields: z.array(z.string()),
|
|
checked_record_ordinals: checkedSchema,
|
|
clock_formats: z
|
|
.record(z.string(), clockFormatSchema)
|
|
.optional()
|
|
.describe(
|
|
"How the view spells clock fields it always spells otherwise than the entities' stored values; a stored value the view never recorded is spelled this way.",
|
|
),
|
|
};
|
|
const viewSchema = z.discriminatedUnion("shape", [
|
|
z.strictObject({
|
|
...entityViewFields,
|
|
shape: z.literal("item"),
|
|
envelope_ordinal: z
|
|
.number()
|
|
.int()
|
|
.nonnegative()
|
|
.describe(
|
|
"Record whose response, with the entity object replaced, every item response shares.",
|
|
),
|
|
projected_record_ordinals: z
|
|
.array(z.number().int().nonnegative())
|
|
.optional()
|
|
.describe(
|
|
"Recorded reads that asked for some fields only (`fields`, `select`) and did not return the item; calls naming a projection replay.",
|
|
),
|
|
}),
|
|
z.strictObject({
|
|
...entityViewFields,
|
|
shape: z.literal("list"),
|
|
membership: z
|
|
.enum(["all", "recorded"])
|
|
.describe(
|
|
"all: every entity of the collection in state order; recorded: the entities each recorded response listed, except on the lines all_record_ordinals names.",
|
|
),
|
|
all_record_ordinals: z
|
|
.array(z.number().int().nonnegative())
|
|
.optional()
|
|
.describe(
|
|
"With recorded membership, the checked lines that each listed every entity of their collection in state order, with only paging parameters starting at zero; a call answered on one lists the whole collection. Absent when none does.",
|
|
),
|
|
counters: z
|
|
.array(z.string())
|
|
.describe("Envelope members that count the listed entities."),
|
|
query: listQuerySchema
|
|
.optional()
|
|
.describe(
|
|
"How the view's other checked lines select and order entities, when filters, a search, a sort or paging explain them; absent when none does.",
|
|
),
|
|
}),
|
|
z.strictObject({
|
|
tool: z.string(),
|
|
shape: z.literal("document"),
|
|
checked_record_ordinals: checkedSchema,
|
|
}),
|
|
z.strictObject({
|
|
tool: z.string(),
|
|
shape: z.literal("recorded"),
|
|
reason: z.enum([
|
|
"query-body",
|
|
"no-init-recording",
|
|
"body-unusable",
|
|
"no-identity",
|
|
"path-varies",
|
|
"query-varies",
|
|
"fields-differ",
|
|
"envelope-varies",
|
|
"list-differs",
|
|
"rendered-oversize",
|
|
]),
|
|
}),
|
|
]);
|
|
|
|
const ordinalSchema = z.number().int().nonnegative();
|
|
const ruled = { field: z.string() };
|
|
const fieldRuleSchema = z.discriminatedUnion("rule", [
|
|
z.strictObject({
|
|
...ruled,
|
|
rule: z.literal("identity"),
|
|
generator: z.enum(["sequence", "uuid", "token"]),
|
|
}),
|
|
z.strictObject({
|
|
...ruled,
|
|
rule: z.literal("sequence"),
|
|
scope: z.enum(["collection", "type"]),
|
|
}),
|
|
z.strictObject({ ...ruled, rule: z.literal("unique") }),
|
|
z.strictObject({
|
|
...ruled,
|
|
rule: z.literal("renewed"),
|
|
pointers: z.array(z.string()).min(1),
|
|
}),
|
|
z.strictObject({
|
|
...ruled,
|
|
rule: z.literal("clock"),
|
|
format: clockFormatSchema,
|
|
}),
|
|
z.strictObject({ ...ruled, rule: z.literal("echo") }),
|
|
z.strictObject({ ...ruled, rule: z.literal("copy"), from: z.string() }),
|
|
z.strictObject({ ...ruled, rule: z.literal("path"), from: z.string() }),
|
|
z.strictObject({ ...ruled, rule: z.literal("slug"), from: z.string() }),
|
|
z.strictObject({ ...ruled, rule: z.literal("template"), from: z.string() }),
|
|
z.strictObject({ ...ruled, rule: z.literal("increment") }),
|
|
z.strictObject({
|
|
...ruled,
|
|
rule: z.literal("constant"),
|
|
value: jsonValueSchema,
|
|
}),
|
|
]);
|
|
const insertRuleSchema = z.union([
|
|
z.strictObject({ rule: z.enum(["first", "last"]) }),
|
|
z.strictObject({
|
|
rule: z.literal("sorted"),
|
|
field: z.string(),
|
|
direction: z.enum(["ascending", "descending"]),
|
|
}),
|
|
]);
|
|
const writePlanSchema = z.strictObject({
|
|
tool: z.string(),
|
|
effect: z.enum(["create", "update", "delete", "document", "none"]),
|
|
target: z
|
|
.string()
|
|
.nullable()
|
|
.describe(
|
|
"The entity type an entity effect writes, or the document view a document write changes.",
|
|
),
|
|
served: z.boolean(),
|
|
reason: z
|
|
.enum([
|
|
"no-state-target",
|
|
"no-call",
|
|
"body-unusable",
|
|
"no-observation",
|
|
"unexplained-change",
|
|
"contradicted",
|
|
"side-effect",
|
|
"unconfirmed",
|
|
"incomplete-entities",
|
|
])
|
|
.nullable(),
|
|
exemplar_ordinal: ordinalSchema
|
|
.nullable()
|
|
.describe("The first successful recorded call the plan was built from."),
|
|
observation: z
|
|
.strictObject({ ordinal: ordinalSchema, pointer: z.string() })
|
|
.nullable()
|
|
.describe(
|
|
"The record whose response showed the written entity or document, and a JSON pointer to it.",
|
|
),
|
|
response: z
|
|
.strictObject({
|
|
ordinal: ordinalSchema,
|
|
shape: z.enum(["entity", "identity", "empty", "recorded"]),
|
|
pointer: z.string().nullable(),
|
|
})
|
|
.nullable(),
|
|
body: z
|
|
.strictObject({
|
|
accepted: z.array(z.string()),
|
|
fixed: z.array(
|
|
z.strictObject({ member: z.string(), sent: jsonValueSchema }),
|
|
),
|
|
required: z.array(z.string()),
|
|
identity: z.string().nullable(),
|
|
kinds: z.array(
|
|
z.strictObject({
|
|
member: z.string(),
|
|
kind: z.enum(["array", "boolean", "number", "object", "string"]),
|
|
}),
|
|
),
|
|
})
|
|
.nullable()
|
|
.describe(
|
|
"Body members the application stored as sent (accepted), members accepted only with their recorded value (fixed), members every call sends (required), and the JSON type a later call stored an accepted member as when the observation does not show it (kinds).",
|
|
),
|
|
rules: z
|
|
.array(fieldRuleSchema)
|
|
.describe(
|
|
"How written fields get their values; unlisted fields of a created entity keep the observed value.",
|
|
),
|
|
collection: z
|
|
.string()
|
|
.nullable()
|
|
.describe(
|
|
"For a create whose recorded values are tied to its collection, the only collection it writes to.",
|
|
),
|
|
insert: insertRuleSchema.nullable(),
|
|
merge: z.enum(["merge", "replace"]).nullable(),
|
|
checked_record_ordinals: z
|
|
.array(ordinalSchema)
|
|
.describe("Later reads that reproduced from the state after this write."),
|
|
contradicted_by: ordinalSchema.nullable(),
|
|
});
|
|
|
|
export const environmentStateFields = {
|
|
origin: z
|
|
.string()
|
|
.describe("Primary origin, as the environment manifest selects it."),
|
|
name: z.string(),
|
|
timeline: z
|
|
.enum(["recorded-start-time", "record-order"])
|
|
.describe(
|
|
"Records are ordered by recorded start time, or by record order when any start time is missing.",
|
|
),
|
|
types: z.array(
|
|
z.strictObject({
|
|
collection_template: z.string(),
|
|
id_field: z.string(),
|
|
id_basis: z
|
|
.enum(["path-parameter", "field-name"])
|
|
.describe(
|
|
"path-parameter: item responses repeat the requested path value in this field; field-name: inferred from listed items as the first identity name, else the first field ending in Id or _id shown distinct across at least two items, that every listed item carries distinctly.",
|
|
),
|
|
init_before_ordinal: z
|
|
.number()
|
|
.int()
|
|
.nonnegative()
|
|
.nullable()
|
|
.describe(
|
|
"First recorded write whose path root relates to the type; state comes from records that finished before it.",
|
|
),
|
|
volatile_fields: z
|
|
.array(z.string())
|
|
.describe(
|
|
"Fields whose recorded values changed with no recorded write between; the state keeps the latest.",
|
|
),
|
|
}),
|
|
),
|
|
entities: z
|
|
.array(
|
|
z.strictObject({
|
|
type: z.string(),
|
|
collection: z.string(),
|
|
id: z.string(),
|
|
fields: jsonObjectFieldsSchema,
|
|
view_fields: z.array(
|
|
z.strictObject({ tool: z.string(), fields: jsonObjectFieldsSchema }),
|
|
),
|
|
record_ordinals: z.array(z.number().int().nonnegative()),
|
|
}),
|
|
)
|
|
.describe(
|
|
"Entities in collection order: type, collection, then first sighting.",
|
|
),
|
|
documents: z.array(
|
|
z.strictObject({
|
|
tool: z.string(),
|
|
path: z.string(),
|
|
value: jsonValueSchema,
|
|
record_ordinal: z.number().int().nonnegative(),
|
|
}),
|
|
),
|
|
views: z.array(viewSchema),
|
|
writes: z
|
|
.array(writePlanSchema)
|
|
.describe(
|
|
"One plan per tool whose method may change state: its modelled effect, and whether the capture's later reads confirmed it (served).",
|
|
),
|
|
write_clock: z
|
|
.string()
|
|
.nullable()
|
|
.describe(
|
|
"The instant a served clock starts from: the end of the capture's last timed record.",
|
|
),
|
|
unknowns: z.array(
|
|
z.strictObject({
|
|
kind: z.enum([
|
|
"authorization-model",
|
|
"response-semantics",
|
|
"state-transition",
|
|
]),
|
|
statement: z.string(),
|
|
}),
|
|
),
|
|
};
|
|
|
|
/** Extract the init state a capture's reads observed and each read tool's render plan; types and identity are inferences. */
|
|
export const projectEnvironmentStateView = (
|
|
parent: AnalysisViewParent,
|
|
view: Extract<AnalysisViewRequest, { kind: "environment-state" }>,
|
|
accumulator = new EnvironmentStateAccumulator(undefined, view.template_rules),
|
|
): Result<UnsignedAnalysisView, AnalysisError> => {
|
|
const grouped = groupCaptureRecords(parent, view.kind, view.template_rules, {
|
|
accumulator,
|
|
noun: "state",
|
|
limitations: [
|
|
"Entity types, identity fields, list membership and counters are inferences from recorded response bodies; the state does not establish server-side schemas, derived values or values copied outside entity objects.",
|
|
],
|
|
});
|
|
if (!grouped.ok) return grouped;
|
|
const state = accumulator.finishState();
|
|
if (!state.ok) return state;
|
|
return ok({
|
|
kind: "environment-state",
|
|
view,
|
|
parent_evidence_id: parent.evidenceId,
|
|
parent_operation: grouped.value.operation,
|
|
parent_digest: parent.evidenceId.slice(3),
|
|
artifact: {
|
|
path: grouped.value.artifact.path,
|
|
sha256: grouped.value.artifact.sha256,
|
|
},
|
|
...state.value,
|
|
});
|
|
};
|