@agent-native/core 0.173.1 → 0.173.2-nightly-20260825163448

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.
Files changed (33) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/desktop/src/app.tsx +77 -7
  3. package/corpus/templates/clips/desktop/src/lib/recorder.ts +91 -25
  4. package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +20 -2
  5. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +7 -1
  6. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  7. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +372 -70
  8. package/corpus/templates/clips/server/lib/resumable-session.ts +15 -0
  9. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +245 -118
  10. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +21 -11
  11. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +132 -25
  12. package/corpus/templates/content/actions/_database-property-input.ts +211 -0
  13. package/corpus/templates/content/actions/_database-row-mutation.ts +151 -26
  14. package/corpus/templates/content/actions/add-database-item.ts +15 -5
  15. package/corpus/templates/content/actions/update-database-item.ts +15 -7
  16. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +15 -5
  17. package/corpus/templates/content/package.json +1 -0
  18. package/corpus/templates/content/parity/README.md +11 -2
  19. package/corpus/templates/content/parity/eval-scenarios.ts +52 -0
  20. package/corpus/templates/content/parity/run-database-create-property-preservation.ts +140 -0
  21. package/corpus/templates/content/parity/scenario-to-eval.ts +390 -0
  22. package/dist/agent/run-store.js +0 -1
  23. package/dist/client/agent-engine-key.js +26 -4
  24. package/dist/client/settings/ProviderSetupForm.js +1 -1
  25. package/dist/collab/struct-routes.d.ts +1 -1
  26. package/dist/eval/agent-runner.js +23 -0
  27. package/dist/eval/types.d.ts +11 -0
  28. package/dist/observability/routes.d.ts +1 -1
  29. package/dist/resources/handlers.d.ts +1 -1
  30. package/dist/server/agent-engine-api-key-route.d.ts +7 -0
  31. package/dist/server/agent-engine-api-key-route.js +38 -0
  32. package/dist/server/realtime-token.d.ts +1 -1
  33. package/package.json +1 -1
@@ -34,11 +34,13 @@ import {
34
34
  } from "./_position-utils.js";
35
35
  import { nanoid } from "./_property-utils.js";
36
36
 
37
+ const databaseMutationAuthorityScopeSchema = z.discriminatedUnion("kind", [
38
+ z.object({ kind: z.literal("personal"), id: z.string().min(1) }),
39
+ z.object({ kind: z.literal("organization"), id: z.string().min(1) }),
40
+ ]);
41
+
37
42
  export const databaseMutationTargetSchema = z.object({
38
- authorityScope: z.discriminatedUnion("kind", [
39
- z.object({ kind: z.literal("personal"), id: z.string().min(1) }),
40
- z.object({ kind: z.literal("organization"), id: z.string().min(1) }),
41
- ]),
43
+ authorityScope: databaseMutationAuthorityScopeSchema,
42
44
  spaceId: z.string().min(1).describe("Exact Content space ID"),
43
45
  databaseId: z.string().min(1).describe("Exact Content database ID"),
44
46
  databaseDocumentId: z
@@ -47,8 +49,35 @@ export const databaseMutationTargetSchema = z.object({
47
49
  .describe("Exact page ID backing the Content database"),
48
50
  });
49
51
 
52
+ export const databaseMutationTargetInputSchema = z.object({
53
+ authorityScope: databaseMutationAuthorityScopeSchema
54
+ .optional()
55
+ .describe(
56
+ "Optional legacy assertion only. Agents must omit it; the authenticated server derives authority from the selected database.",
57
+ ),
58
+ spaceId: z
59
+ .string()
60
+ .min(1)
61
+ .describe("Exact Content space ID returned by database discovery"),
62
+ databaseId: z
63
+ .string()
64
+ .min(1)
65
+ .describe(
66
+ "Exact Content database ID returned by database discovery; never derive it from a title or number in the request",
67
+ ),
68
+ databaseDocumentId: z
69
+ .string()
70
+ .min(1)
71
+ .describe(
72
+ "Exact page ID backing the database, returned by database discovery",
73
+ ),
74
+ });
75
+
76
+ export const databaseMutationAgentTargetSchema =
77
+ databaseMutationTargetInputSchema.omit({ authorityScope: true });
78
+
50
79
  export const databaseMutationEnvelopeSchema = z.object({
51
- target: databaseMutationTargetSchema,
80
+ target: databaseMutationTargetInputSchema,
52
81
  expectedSchemaRevision: z
53
82
  .string()
54
83
  .min(1)
@@ -59,6 +88,9 @@ export const databaseMutationEnvelopeSchema = z.object({
59
88
  export type DatabaseMutationTarget = z.infer<
60
89
  typeof databaseMutationTargetSchema
61
90
  >;
91
+ export type DatabaseMutationTargetInput = z.infer<
92
+ typeof databaseMutationTargetInputSchema
93
+ >;
62
94
 
63
95
  type DatabaseRow = typeof schema.contentDatabases.$inferSelect;
64
96
  type DefinitionRow = typeof schema.documentPropertyDefinitions.$inferSelect;
@@ -82,11 +114,12 @@ export interface RowSnapshot {
82
114
  export type DatabaseRowMutationOperation = "create" | "update" | "upsert";
83
115
 
84
116
  export interface CreateDatabaseRowMutationInput {
85
- target: DatabaseMutationTarget;
117
+ target: DatabaseMutationTargetInput;
86
118
  expectedSchemaRevision: string;
87
119
  idempotencyKey: string;
88
120
  title?: string;
89
121
  propertyValues?: Record<string, unknown>;
122
+ propertyTypeAssertions?: Record<string, string>;
90
123
  }
91
124
 
92
125
  export interface UpdateDatabaseRowMutationInput extends CreateDatabaseRowMutationInput {
@@ -211,7 +244,7 @@ function acceptedShape(type: DocumentPropertyType): string {
211
244
  }
212
245
 
213
246
  export async function loadContext(
214
- target: DatabaseMutationTarget,
247
+ target: DatabaseMutationTargetInput,
215
248
  role: "viewer" | "editor",
216
249
  db: Db = getDb(),
217
250
  accessAlreadyResolved = false,
@@ -260,8 +293,9 @@ export async function loadContext(
260
293
  ? { kind: "organization" as const, id: database.orgId }
261
294
  : { kind: "personal" as const, id: database.ownerEmail };
262
295
  if (
263
- target.authorityScope.kind !== authorityScope.kind ||
264
- target.authorityScope.id !== authorityScope.id ||
296
+ (target.authorityScope !== undefined &&
297
+ (target.authorityScope.kind !== authorityScope.kind ||
298
+ target.authorityScope.id !== authorityScope.id)) ||
265
299
  database.spaceId !== target.spaceId ||
266
300
  database.documentId !== target.databaseDocumentId ||
267
301
  databaseDocument.spaceId !== target.spaceId ||
@@ -322,7 +356,12 @@ export async function getDatabaseMutationContract(
322
356
  );
323
357
  return {
324
358
  target: {
325
- ...target,
359
+ authorityScope: context.database.orgId
360
+ ? { kind: "organization", id: context.database.orgId }
361
+ : { kind: "personal", id: context.database.ownerEmail },
362
+ spaceId: context.database.spaceId!,
363
+ databaseId: context.database.id,
364
+ databaseDocumentId: context.database.documentId,
326
365
  },
327
366
  schemaRevision: context.schemaRevision,
328
367
  naturalKeyPropertyId: context.database.naturalKeyPropertyId,
@@ -695,14 +734,72 @@ export function revisionPropertyIds(context: MutationContext) {
695
734
  );
696
735
  }
697
736
 
698
- function payloadDigest(
737
+ export function databaseMutationPayloadDigest(
699
738
  operation: DatabaseRowMutationOperation,
700
739
  input:
701
740
  | CreateDatabaseRowMutationInput
702
741
  | UpdateDatabaseRowMutationInput
703
742
  | UpsertDatabaseRowMutationInput,
704
743
  ) {
705
- return digest({ operation, ...input });
744
+ const {
745
+ propertyTypeAssertions: _propertyTypeAssertions,
746
+ target,
747
+ ...canonicalInput
748
+ } = input;
749
+ const { authorityScope: _authorityScope, ...stableTarget } = target ?? {};
750
+ return digest({ operation, ...canonicalInput, target: stableTarget });
751
+ }
752
+
753
+ export function legacyDatabaseMutationPayloadDigest(
754
+ operation: DatabaseRowMutationOperation,
755
+ input:
756
+ | CreateDatabaseRowMutationInput
757
+ | UpdateDatabaseRowMutationInput
758
+ | UpsertDatabaseRowMutationInput,
759
+ authorityScope = input.target.authorityScope,
760
+ ) {
761
+ const { propertyTypeAssertions: _propertyTypeAssertions, ...legacyInput } =
762
+ input;
763
+ return digest({
764
+ operation,
765
+ ...legacyInput,
766
+ target: { ...legacyInput.target, authorityScope },
767
+ });
768
+ }
769
+
770
+ function authorityScopeForContext(context: MutationContext) {
771
+ return context.database.orgId
772
+ ? ({ kind: "organization", id: context.database.orgId } as const)
773
+ : ({ kind: "personal", id: context.database.ownerEmail } as const);
774
+ }
775
+
776
+ function assertPropertyTypeAssertions(
777
+ context: MutationContext,
778
+ assertions: Record<string, string> | undefined,
779
+ ) {
780
+ if (!assertions) return;
781
+ const definitionsById = new Map(
782
+ context.definitions.map((definition) => [definition.id, definition]),
783
+ );
784
+ for (const [propertyId, assertedType] of Object.entries(assertions)) {
785
+ const definition = definitionsById.get(propertyId);
786
+ if (!definition) {
787
+ throw new ActionContractError(
788
+ `Unknown property definition "${propertyId}".`,
789
+ {
790
+ errorCode: "UNKNOWN_PROPERTY",
791
+ details: { propertyId },
792
+ statusCode: 400,
793
+ },
794
+ );
795
+ }
796
+ if (definition.type !== assertedType) {
797
+ invalidProperty(
798
+ definition,
799
+ `typed entry declared propertyType "${assertedType}" but the discovered property type is "${definition.type}"`,
800
+ );
801
+ }
802
+ }
706
803
  }
707
804
 
708
805
  function resultForReceipt(
@@ -721,9 +818,7 @@ function resultForReceipt(
721
818
  },
722
819
  ): ContentDatabaseRowMutationResult {
723
820
  const target = {
724
- authorityScope: context.database.orgId
725
- ? ({ kind: "organization", id: context.database.orgId } as const)
726
- : ({ kind: "personal", id: context.database.ownerEmail } as const),
821
+ authorityScope: authorityScopeForContext(context),
727
822
  spaceId: context.database.spaceId!,
728
823
  databaseId: context.database.id,
729
824
  databaseDocumentId: context.database.documentId,
@@ -770,7 +865,7 @@ function resultForReceipt(
770
865
  async function replayReceipt(
771
866
  context: MutationContext,
772
867
  idempotencyKey: string,
773
- expectedPayloadDigest: string,
868
+ expectedPayloadDigests: readonly string[],
774
869
  db: Db = getDb(),
775
870
  ): Promise<ContentDatabaseRowMutationResult | null> {
776
871
  const [stored] = await db
@@ -789,7 +884,7 @@ async function replayReceipt(
789
884
  ),
790
885
  );
791
886
  if (!stored) return null;
792
- if (stored.payloadDigest !== expectedPayloadDigest) {
887
+ if (!expectedPayloadDigests.includes(stored.payloadDigest)) {
793
888
  conflict(
794
889
  "IDEMPOTENCY_KEY_REUSED",
795
890
  "This idempotency key was already used for a different row mutation.",
@@ -1137,13 +1232,22 @@ export async function createDatabaseRow(
1137
1232
  input: CreateDatabaseRowMutationInput,
1138
1233
  ): Promise<ContentDatabaseRowMutationResult> {
1139
1234
  const initial = await loadContext(input.target, "editor");
1140
- const inputDigest = payloadDigest("create", input);
1235
+ const inputDigest = databaseMutationPayloadDigest("create", input);
1236
+ const replayDigests = [
1237
+ inputDigest,
1238
+ legacyDatabaseMutationPayloadDigest(
1239
+ "create",
1240
+ input,
1241
+ authorityScopeForContext(initial),
1242
+ ),
1243
+ ];
1141
1244
  const replay = await replayReceipt(
1142
1245
  initial,
1143
1246
  input.idempotencyKey,
1144
- inputDigest,
1247
+ replayDigests,
1145
1248
  );
1146
1249
  if (replay) return replay;
1250
+ assertPropertyTypeAssertions(initial, input.propertyTypeAssertions);
1147
1251
  assertSchema(initial, input.expectedSchemaRevision);
1148
1252
  const values = await normalizePatch(initial, input.propertyValues);
1149
1253
  const result = await withMutationLocks(initial.database, () =>
@@ -1160,10 +1264,11 @@ export async function createDatabaseRow(
1160
1264
  const lockedReplay = await replayReceipt(
1161
1265
  locked,
1162
1266
  input.idempotencyKey,
1163
- inputDigest,
1267
+ replayDigests,
1164
1268
  tx as unknown as Db,
1165
1269
  );
1166
1270
  if (lockedReplay) return lockedReplay;
1271
+ assertPropertyTypeAssertions(locked, input.propertyTypeAssertions);
1167
1272
  assertSchema(locked, input.expectedSchemaRevision);
1168
1273
  await touchContentDatabase(
1169
1274
  tx as unknown as Db,
@@ -1206,13 +1311,22 @@ export async function updateDatabaseRow(
1206
1311
  ): Promise<ContentDatabaseRowMutationResult> {
1207
1312
  const initial = await loadContext(input.target, "editor");
1208
1313
  await assertAccess("document", input.documentId, "editor");
1209
- const inputDigest = payloadDigest("update", input);
1314
+ const inputDigest = databaseMutationPayloadDigest("update", input);
1315
+ const replayDigests = [
1316
+ inputDigest,
1317
+ legacyDatabaseMutationPayloadDigest(
1318
+ "update",
1319
+ input,
1320
+ authorityScopeForContext(initial),
1321
+ ),
1322
+ ];
1210
1323
  const replay = await replayReceipt(
1211
1324
  initial,
1212
1325
  input.idempotencyKey,
1213
- inputDigest,
1326
+ replayDigests,
1214
1327
  );
1215
1328
  if (replay) return replay;
1329
+ assertPropertyTypeAssertions(initial, input.propertyTypeAssertions);
1216
1330
  assertSchema(initial, input.expectedSchemaRevision);
1217
1331
  const values = await normalizePatch(initial, input.propertyValues);
1218
1332
  const result = await withMutationLocks(initial.database, () =>
@@ -1229,10 +1343,11 @@ export async function updateDatabaseRow(
1229
1343
  const lockedReplay = await replayReceipt(
1230
1344
  locked,
1231
1345
  input.idempotencyKey,
1232
- inputDigest,
1346
+ replayDigests,
1233
1347
  tx as unknown as Db,
1234
1348
  );
1235
1349
  if (lockedReplay) return lockedReplay;
1350
+ assertPropertyTypeAssertions(locked, input.propertyTypeAssertions);
1236
1351
  assertSchema(locked, input.expectedSchemaRevision);
1237
1352
  const updated = await updateInsideTransaction(
1238
1353
  tx as unknown as Db,
@@ -1305,13 +1420,22 @@ export async function upsertDatabaseRow(
1305
1420
  "must match the upsert keyValue when provided in propertyValues",
1306
1421
  );
1307
1422
  }
1308
- const inputDigest = payloadDigest("upsert", input);
1423
+ const inputDigest = databaseMutationPayloadDigest("upsert", input);
1424
+ const replayDigests = [
1425
+ inputDigest,
1426
+ legacyDatabaseMutationPayloadDigest(
1427
+ "upsert",
1428
+ input,
1429
+ authorityScopeForContext(initial),
1430
+ ),
1431
+ ];
1309
1432
  const replay = await replayReceipt(
1310
1433
  initial,
1311
1434
  input.idempotencyKey,
1312
- inputDigest,
1435
+ replayDigests,
1313
1436
  );
1314
1437
  if (replay) return replay;
1438
+ assertPropertyTypeAssertions(initial, input.propertyTypeAssertions);
1315
1439
  assertSchema(initial, input.expectedSchemaRevision);
1316
1440
  const keyPropertyId = initial.database.naturalKeyPropertyId;
1317
1441
  if (!keyPropertyId) {
@@ -1368,10 +1492,11 @@ export async function upsertDatabaseRow(
1368
1492
  const lockedReplay = await replayReceipt(
1369
1493
  locked,
1370
1494
  input.idempotencyKey,
1371
- inputDigest,
1495
+ replayDigests,
1372
1496
  tx as unknown as Db,
1373
1497
  );
1374
1498
  if (lockedReplay) return lockedReplay;
1499
+ assertPropertyTypeAssertions(locked, input.propertyTypeAssertions);
1375
1500
  assertSchema(locked, input.expectedSchemaRevision);
1376
1501
  if (locked.database.naturalKeyPropertyId !== keyPropertyId) {
1377
1502
  conflict("SCHEMA_REVISION_CONFLICT", "The natural key changed.");
@@ -3,8 +3,14 @@ import { buildDeepLink } from "@agent-native/core/server";
3
3
  import { z } from "zod";
4
4
 
5
5
  import type { ContentDatabaseRowMutationResult } from "../shared/api.js";
6
+ import {
7
+ canonicalizeDatabasePropertyInput,
8
+ databasePropertyEntriesSchema,
9
+ databasePropertyValuesSchema,
10
+ } from "./_database-property-input.js";
6
11
  import {
7
12
  createDatabaseRow,
13
+ databaseMutationAgentTargetSchema,
8
14
  databaseMutationEnvelopeSchema,
9
15
  } from "./_database-row-mutation.js";
10
16
  import { getContentDatabaseResponse } from "./_database-utils.js";
@@ -17,15 +23,17 @@ const schema = databaseMutationEnvelopeSchema.extend({
17
23
  .max(500)
18
24
  .optional()
19
25
  .describe("New row page title"),
20
- propertyValues: z
21
- .record(z.string(), z.unknown())
22
- .optional()
23
- .describe("Strict property values keyed by property definition ID"),
26
+ propertyValues: databasePropertyValuesSchema,
27
+ propertyEntries: databasePropertyEntriesSchema,
24
28
  });
29
+ const agentSchema = schema
30
+ .extend({ target: databaseMutationAgentTargetSchema })
31
+ .omit({ propertyValues: true });
25
32
 
26
33
  export default defineAction({
27
34
  description:
28
35
  "Create one row in an exact ordinary Content database using its discovered schema revision. Strictly validates every non-Blocks property, applies the side effect once per idempotency key, and returns a verified receipt with stable row identity.",
36
+ agentInputSchema: agentSchema,
29
37
  publicAgent: {
30
38
  expose: true,
31
39
  readOnly: false,
@@ -52,7 +60,9 @@ export default defineAction({
52
60
  },
53
61
  },
54
62
  run: async (args): Promise<ContentDatabaseRowMutationResult> => {
55
- const result = await createDatabaseRow(args);
63
+ const result = await createDatabaseRow(
64
+ canonicalizeDatabasePropertyInput(args),
65
+ );
56
66
  const response = await getContentDatabaseResponse(
57
67
  result.receipt.target.databaseId,
58
68
  {
@@ -4,6 +4,12 @@ import { z } from "zod";
4
4
 
5
5
  import type { ContentDatabaseRowMutationResult } from "../shared/api.js";
6
6
  import {
7
+ canonicalizeDatabasePropertyInput,
8
+ databasePropertyEntriesSchema,
9
+ databasePropertyValuesSchema,
10
+ } from "./_database-property-input.js";
11
+ import {
12
+ databaseMutationAgentTargetSchema,
7
13
  databaseMutationEnvelopeSchema,
8
14
  updateDatabaseRow,
9
15
  } from "./_database-row-mutation.js";
@@ -16,17 +22,19 @@ const schema = databaseMutationEnvelopeSchema.extend({
16
22
  .min(1)
17
23
  .describe("Row revision returned by get-content-database"),
18
24
  title: z.string().trim().min(1).max(500).optional(),
19
- propertyValues: z
20
- .record(z.string(), z.unknown())
21
- .optional()
22
- .describe(
23
- "Sparse strict patch keyed by property definition ID; omitted fields are preserved and explicit null clears a value",
24
- ),
25
+ propertyValues: databasePropertyValuesSchema,
26
+ propertyEntries: databasePropertyEntriesSchema.describe(
27
+ "Sparse typed property patch as explicit entries; omitted fields are preserved and explicit null clears a value. Copy each propertyType from the discovered mutation contract and include one entry for every writable property value the user requested, using the exact immutable property definition ID. When at least one value was requested, never pass an empty array. Do not invent or clear unmentioned properties.",
28
+ ),
25
29
  });
30
+ const agentSchema = schema
31
+ .extend({ target: databaseMutationAgentTargetSchema })
32
+ .omit({ propertyValues: true });
26
33
 
27
34
  export default defineAction({
28
35
  description:
29
36
  "Sparsely update one exact Content database row by stable item and document IDs. Requires schema and row revisions, validates every non-Blocks property, and returns a verified idempotent receipt.",
37
+ agentInputSchema: agentSchema,
30
38
  schema,
31
39
  http: { method: "PUT" },
32
40
  audit: {
@@ -44,7 +52,7 @@ export default defineAction({
44
52
  : "Updated Content database row";
45
53
  },
46
54
  },
47
- run: updateDatabaseRow,
55
+ run: (args) => updateDatabaseRow(canonicalizeDatabasePropertyInput(args)),
48
56
  link: ({ result }) => {
49
57
  const documentId = (result as ContentDatabaseRowMutationResult | null)
50
58
  ?.receipt.row.documentId;
@@ -4,6 +4,12 @@ import { z } from "zod";
4
4
 
5
5
  import type { ContentDatabaseRowMutationResult } from "../shared/api.js";
6
6
  import {
7
+ canonicalizeDatabasePropertyInput,
8
+ databasePropertyEntriesSchema,
9
+ databasePropertyValuesSchema,
10
+ } from "./_database-property-input.js";
11
+ import {
12
+ databaseMutationAgentTargetSchema,
7
13
  databaseMutationEnvelopeSchema,
8
14
  upsertDatabaseRow,
9
15
  } from "./_database-row-mutation.js";
@@ -18,15 +24,19 @@ const schema = databaseMutationEnvelopeSchema.extend({
18
24
  "Use null to assert the key is absent and create; use the discovered row revision to update an existing key",
19
25
  ),
20
26
  title: z.string().trim().min(1).max(500).optional(),
21
- propertyValues: z
22
- .record(z.string(), z.unknown())
23
- .optional()
24
- .describe("Sparse strict values keyed by property definition ID"),
27
+ propertyValues: databasePropertyValuesSchema,
28
+ propertyEntries: databasePropertyEntriesSchema.describe(
29
+ "Sparse typed property values as explicit entries. Copy each propertyType from the discovered mutation contract and include one entry for every writable property value the user requested, using the exact immutable property definition ID. When at least one value was requested, never pass an empty array. Do not invent or clear unmentioned properties.",
30
+ ),
25
31
  });
32
+ const agentSchema = schema
33
+ .extend({ target: databaseMutationAgentTargetSchema })
34
+ .omit({ propertyValues: true });
26
35
 
27
36
  export default defineAction({
28
37
  description:
29
38
  "Create or sparsely update one Content database row by that database's explicitly configured natural key. Requires schema and row compare-and-swap revisions and returns a verified idempotent receipt.",
39
+ agentInputSchema: agentSchema,
30
40
  schema,
31
41
  audit: {
32
42
  recordInputs: false,
@@ -43,7 +53,7 @@ export default defineAction({
43
53
  : "Upserted Content database row by natural key";
44
54
  },
45
55
  },
46
- run: upsertDatabaseRow,
56
+ run: (args) => upsertDatabaseRow(canonicalizeDatabasePropertyInput(args)),
47
57
  link: ({ result }) => {
48
58
  const documentId = (result as ContentDatabaseRowMutationResult | null)
49
59
  ?.receipt.row.documentId;
@@ -12,6 +12,7 @@
12
12
  "test:parity": "vitest --run parity",
13
13
  "test:parity-capabilities": "vitest --run parity actions/content-database-lifecycle.db.test.ts actions/bind-content-database-source-field.db.test.ts actions/_local-file-documents.test.ts actions/builder-source-review-gates.db.test.ts",
14
14
  "eval:parity": "agent-native eval parity",
15
+ "eval:property-preservation": "tsx parity/run-database-create-property-preservation.ts",
15
16
  "format.fix": "oxfmt --write .",
16
17
  "typecheck": "agent-native typecheck",
17
18
  "migrate:production": "tsx scripts/migrate-production.ts",
@@ -10,7 +10,7 @@ PR 2.2 adds two executable tiers:
10
10
  existing action tests, make no model calls, and require no private provider
11
11
  credentials.
12
12
  - Gated agent evals run through `agent-native eval parity`. They are opt-in via
13
- `CONTENT_PARITY_EVALS=1`, capped to four initial scenarios, and should be
13
+ `CONTENT_PARITY_EVALS=1`, kept to a small explicit scenario set, and should be
14
14
  reserved for manual or nightly checks.
15
15
 
16
16
  ## Deterministic Checks
@@ -33,14 +33,23 @@ cd templates/content
33
33
  CONTENT_PARITY_EVALS=1 ANTHROPIC_API_KEY=... ./node_modules/.bin/agent-native eval parity
34
34
  ```
35
35
 
36
+ The database-create property-preservation regression has a dedicated
37
+ fixture-only runner so it can inspect model-produced arguments without loading
38
+ or executing unrelated Content actions:
39
+
40
+ ```bash
41
+ CONTENT_PARITY_EVALS=1 pnpm eval:property-preservation
42
+ ```
43
+
36
44
  With `CONTENT_PARITY_EVALS` unset, parity evals return skipped rows and do not
37
45
  call the agent runner. The CLI still exits `0`, but both readable and JSON
38
46
  reports mark each row with `status: "skipped"` and a `skipReason` such as
39
47
  `Skipped because CONTENT_PARITY_EVALS is unset`.
40
48
 
41
- With the gate set, the eval files run the four PR 2.2 scenarios:
49
+ With the gate set, the eval files include these scenarios:
42
50
 
43
51
  - `database-source-scope`
52
+ - `database-create-property-preservation`
44
53
  - `document-search-edit`
45
54
  - `local-file-source-truth`
46
55
  - `builder-source-review-readonly`
@@ -8,9 +8,61 @@ export interface ParityEvalScenario {
8
8
  prompt: string;
9
9
  successSignals: string[];
10
10
  expectedTools?: string[];
11
+ expectedPropertyValues?: Record<string, unknown>;
12
+ expectedPropertyTypes?: Record<string, string>;
13
+ expectedCreateEnvelope?: {
14
+ target: {
15
+ spaceId: string;
16
+ databaseId: string;
17
+ databaseDocumentId: string;
18
+ };
19
+ expectedSchemaRevision: string;
20
+ idempotencyKey: string;
21
+ title: string;
22
+ };
11
23
  }
12
24
 
13
25
  export const parityEvalScenarios: ParityEvalScenario[] = [
26
+ {
27
+ id: "database-create-property-preservation",
28
+ title: "Database create property preservation",
29
+ capabilityIds: ["database.rows"],
30
+ gateEnv: "CONTENT_PARITY_EVALS",
31
+ defaultState: "skipped",
32
+ requiresPrivateCredentials: false,
33
+ prompt:
34
+ "Find the fixture Content database titled PR #3314 feedback — do not treat 3314 as its database ID. Discover its exact database target and mutation contract through Content actions, then create exactly one row titled [FIXTURE] preserve explicit properties with idempotency key fixture-create-property-preservation-v1. Set Status to status-cannot-verify and Evidence to Baseline fixture preserve-me using the discovered immutable property IDs and property types. Omit authority identity because the authenticated server owns it. No Blocks value was requested. Call add-database-item once, then report its result truthfully.",
35
+ successSignals: [
36
+ "Discovers the exact database and mutation contract before creating.",
37
+ "Uses add-database-item once for the discovered stable target without authoring authority identity.",
38
+ "Preserves both explicitly requested writable property values.",
39
+ "Does not invent a Blocks value or another property.",
40
+ "Reports an action failure rather than claiming a row was created if the fixture is unavailable.",
41
+ ],
42
+ expectedTools: [
43
+ "list-content-databases",
44
+ "get-content-database",
45
+ "add-database-item",
46
+ ],
47
+ expectedPropertyValues: {
48
+ fixture_status_property: "status-cannot-verify",
49
+ fixture_evidence_property: "Baseline fixture preserve-me",
50
+ },
51
+ expectedPropertyTypes: {
52
+ fixture_status_property: "status",
53
+ fixture_evidence_property: "text",
54
+ },
55
+ expectedCreateEnvelope: {
56
+ target: {
57
+ spaceId: "fixture_personal_space",
58
+ databaseId: "fixture_feedback_database",
59
+ databaseDocumentId: "fixture_feedback_document",
60
+ },
61
+ expectedSchemaRevision: "fixture_schema_revision",
62
+ idempotencyKey: "fixture-create-property-preservation-v1",
63
+ title: "[FIXTURE] preserve explicit properties",
64
+ },
65
+ },
14
66
  {
15
67
  id: "database-bulk-row-reliability",
16
68
  title: "Bulk database row reliability",