@things-factory/ai-assistant 10.1.107 → 10.1.108

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 (83) hide show
  1. package/client/components/assistant-chat.ts +67 -2
  2. package/client/components/assistant-dock.ts +4 -4
  3. package/client/components/assistant-mode.ts +1 -1
  4. package/client/components/entry-review.ts +23 -3
  5. package/client/pages/assistant-workspace-page.ts +82 -2
  6. package/client/utils/attachment-policy.ts +22 -3
  7. package/client/utils/entry-hash.ts +35 -0
  8. package/client/utils/entry-proposal.ts +10 -3
  9. package/client/utils/entry-workspace.ts +70 -0
  10. package/client/utils/pasted-table.ts +145 -0
  11. package/client/utils/spreadsheet-text.ts +125 -0
  12. package/dist-client/components/assistant-chat.d.ts +2 -0
  13. package/dist-client/components/assistant-chat.js +63 -2
  14. package/dist-client/components/assistant-chat.js.map +1 -1
  15. package/dist-client/components/assistant-dock.js +4 -4
  16. package/dist-client/components/assistant-dock.js.map +1 -1
  17. package/dist-client/components/assistant-mode.d.ts +2 -1
  18. package/dist-client/components/assistant-mode.js.map +1 -1
  19. package/dist-client/components/entry-review.d.ts +2 -0
  20. package/dist-client/components/entry-review.js +30 -3
  21. package/dist-client/components/entry-review.js.map +1 -1
  22. package/dist-client/pages/assistant-workspace-page.d.ts +8 -0
  23. package/dist-client/pages/assistant-workspace-page.js +98 -2
  24. package/dist-client/pages/assistant-workspace-page.js.map +1 -1
  25. package/dist-client/tsconfig.tsbuildinfo +1 -1
  26. package/dist-client/utils/attachment-policy.d.ts +7 -1
  27. package/dist-client/utils/attachment-policy.js +14 -2
  28. package/dist-client/utils/attachment-policy.js.map +1 -1
  29. package/dist-client/utils/entry-hash.d.ts +2 -0
  30. package/dist-client/utils/entry-hash.js +36 -0
  31. package/dist-client/utils/entry-hash.js.map +1 -0
  32. package/dist-client/utils/entry-proposal.d.ts +1 -1
  33. package/dist-client/utils/entry-proposal.js +9 -2
  34. package/dist-client/utils/entry-proposal.js.map +1 -1
  35. package/dist-client/utils/entry-workspace.d.ts +20 -0
  36. package/dist-client/utils/entry-workspace.js +68 -0
  37. package/dist-client/utils/entry-workspace.js.map +1 -0
  38. package/dist-client/utils/pasted-table.d.ts +34 -0
  39. package/dist-client/utils/pasted-table.js +150 -0
  40. package/dist-client/utils/pasted-table.js.map +1 -0
  41. package/dist-client/utils/spreadsheet-text.d.ts +48 -0
  42. package/dist-client/utils/spreadsheet-text.js +110 -0
  43. package/dist-client/utils/spreadsheet-text.js.map +1 -0
  44. package/dist-server/service/assistant-chat-resolver.js +1 -1
  45. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  46. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +1 -1
  47. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  48. package/dist-server/service/chat-session/attachment-policy.d.ts +5 -2
  49. package/dist-server/service/chat-session/attachment-policy.js +10 -1
  50. package/dist-server/service/chat-session/attachment-policy.js.map +1 -1
  51. package/dist-server/service/chat-session/attachment-tools.js +3 -1
  52. package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
  53. package/dist-server/service/chat-session/session-attachments.d.ts +3 -2
  54. package/dist-server/service/chat-session/session-attachments.js +5 -4
  55. package/dist-server/service/chat-session/session-attachments.js.map +1 -1
  56. package/dist-server/service/data-entry/entry-apply.d.ts +2 -0
  57. package/dist-server/service/data-entry/entry-apply.js +4 -0
  58. package/dist-server/service/data-entry/entry-apply.js.map +1 -1
  59. package/dist-server/service/data-entry/entry-hash.d.ts +2 -0
  60. package/dist-server/service/data-entry/entry-hash.js +39 -0
  61. package/dist-server/service/data-entry/entry-hash.js.map +1 -0
  62. package/dist-server/service/data-entry/entry-resolver.d.ts +1 -1
  63. package/dist-server/service/data-entry/entry-resolver.js +5 -3
  64. package/dist-server/service/data-entry/entry-resolver.js.map +1 -1
  65. package/dist-server/tsconfig.tsbuildinfo +1 -1
  66. package/package.json +3 -2
  67. package/server/service/assistant-chat-resolver.ts +2 -2
  68. package/server/service/assistant-mode/assistant-mode-resolver.ts +1 -1
  69. package/server/service/chat-session/attachment-policy.ts +18 -2
  70. package/server/service/chat-session/attachment-tools.ts +3 -1
  71. package/server/service/chat-session/session-attachments.ts +5 -4
  72. package/server/service/data-entry/data-entry.test.ts +10 -0
  73. package/server/service/data-entry/entry-apply.ts +5 -0
  74. package/server/service/data-entry/entry-hash.ts +35 -0
  75. package/server/service/data-entry/entry-resolver.ts +4 -2
  76. package/test/entry-workspace.test.ts +89 -0
  77. package/test/pasted-table.test.ts +140 -0
  78. package/test/spreadsheet-text.test.ts +92 -0
  79. package/translations/en.json +12 -1
  80. package/translations/ja.json +12 -1
  81. package/translations/ko.json +12 -1
  82. package/translations/ms.json +12 -1
  83. package/translations/zh.json +12 -1
@@ -1 +1 @@
1
- {"version":3,"file":"session-attachments.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-attachments.ts"],"names":[],"mappings":";;;AAsBA,gDAEC;AAGD,kDAOC;AAGD,gDAMC;AAMD,gDAYC;AAGD,oDAWC;AAGD,gDAUC;AAxFD;;;;;;;GAOG;AACH,qEAAuF;AACvF,iDAAqD;AAErD,iEAA+E;AAElE,QAAA,mBAAmB,GAAG,cAAc,CAAA;AASjD,SAAgB,kBAAkB,CAAC,GAAQ;IACzC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAC5F,CAAC;AAED,gHAAgH;AACzG,KAAK,UAAU,mBAAmB,CAAC,SAAiB,EAAE,KAAY,EAAE,OAAY;IACrF,MAAM,KAAK,GAA2B,EAAE,CAAA;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,IAAA,kCAAgB,EAAC,IAAI,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,2BAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;QAC7I,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,qFAAqF;AAC9E,KAAK,UAAU,kBAAkB,CAAC,QAAgB,EAAE,SAAiB,EAAE,YAAoB;IAChG,OAAO,CACL,CAAC,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,OAAO,CAAC;QACvC,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,2BAAmB,EAAE,KAAK,EAAE,SAAS,EAAS;KAC7G,CAAC,CAAC,IAAI,SAAS,CACjB,CAAA;AACH,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,kBAAkB,CAAC,GAAQ;IAI/C,MAAM,IAAI,GAAG,IAAA,uCAAgB,EAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IACrE,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,MAAM,yBAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC7C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAA;IAC3H,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,yBAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,2CAAoB,CAAA;IACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,2CAAoB,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAA;AACxH,CAAC;AAED,uDAAuD;AAChD,KAAK,UAAU,oBAAoB,CAAC,GAAa;IACtD,IAAI,CAAC,GAAG,CAAC,MAAM;QAAE,OAAM;IACvB,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,2BAAmB,EAAE,CAAC,CAAQ,EAAE,CAAC,CAAA;IAC1H,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,yBAAO,CAAC,UAAU,CAAE,GAAW,CAAC,IAAI,CAAC,CAAA;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,MAAM;QAAE,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAE,GAAW,CAAC,EAAE,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED,4FAA4F;AACrF,KAAK,UAAU,kBAAkB,CAAC,OAAkD;IACzF,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,2BAAmB,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,EAAS,EAAE,CAAC,CAAA;IACxH,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,yBAAO,CAAC,UAAU,CAAE,GAAW,CAAC,IAAI,CAAC,CAAA;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,MAAM;QAAE,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAE,GAAW,CAAC,EAAE,CAAC,CAAC,CAAA;AAC3F,CAAC","sourcesContent":["/*\n * A conversation's files - stored in attachment-base, opened only through the conversation.\n *\n * Files are kept with `refType: 'chat-session'`, `refBy: <session id>` (design/ai/shared-dock.md §9-2).\n * They are never handed out through attachment-base's own route: that route is public and unauthenticated\n * (routes.ts, \"TODO make this secure\"), so a private conversation's file would leave the conversation.\n * Every read here passes the conversation's gate first (§9-3); this module only does the storage part.\n */\nimport { Attachment, STORAGE, createAttachment } from '@things-factory/attachment-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { attachmentKindOf, TEXT_CHARS_FOR_MODEL } from './attachment-policy.js'\n\nexport const CHAT_ATTACHMENT_REF = 'chat-session'\n\nexport interface StoredChatAttachment {\n id: string\n name: string\n mimetype: string\n size: number\n}\n\nexport function describeAttachment(row: any): StoredChatAttachment {\n return { id: row.id, name: row.name, mimetype: row.mimetype, size: Number(row.size) || 0 }\n}\n\n/** Stores uploaded files under the conversation. The caller has passed the write gate and the mode's policy. */\nexport async function saveChatAttachments(sessionId: string, files: any[], context: any): Promise<StoredChatAttachment[]> {\n const saved: StoredChatAttachment[] = []\n for (const file of files) {\n const row = await createAttachment(null, { attachment: { file, category: 'chat', refType: CHAT_ATTACHMENT_REF, refBy: sessionId } }, context)\n saved.push(describeAttachment(row))\n }\n return saved\n}\n\n/** One file of the conversation - `undefined` when it is not this conversation's. */\nexport async function findChatAttachment(domainId: string, sessionId: string, attachmentId: string): Promise<any | undefined> {\n return (\n (await getRepository(Attachment).findOne({\n where: { id: attachmentId, domain: { id: domainId }, refType: CHAT_ATTACHMENT_REF, refBy: sessionId } as any\n })) ?? undefined\n )\n}\n\n/**\n * What the model gets for a file: an image as bytes, a text file as its text (cut at TEXT_CHARS_FOR_MODEL,\n * and the cut is said), anything else as a line that names it.\n */\nexport async function attachmentForModel(row: any): Promise<\n | { type: 'image'; data: Buffer; mediaType: string; name: string }\n | { type: 'text'; text: string; name: string; truncated: boolean }\n> {\n const kind = attachmentKindOf({ type: row.mimetype, name: row.name })\n if (kind === 'image') {\n const data = await STORAGE.readFile(row.path)\n return { type: 'image', data: Buffer.isBuffer(data) ? data : Buffer.from(data), mediaType: row.mimetype, name: row.name }\n }\n const content = String(await STORAGE.readFile(row.path, 'utf8'))\n const truncated = content.length > TEXT_CHARS_FOR_MODEL\n return { type: 'text', text: truncated ? content.slice(0, TEXT_CHARS_FOR_MODEL) : content, name: row.name, truncated }\n}\n\n/** Removes these files from storage and their rows. */\nexport async function removeAttachmentRows(ids: string[]): Promise<void> {\n if (!ids.length) return\n const rows = await getRepository(Attachment).find({ where: ids.map(id => ({ id, refType: CHAT_ATTACHMENT_REF })) as any })\n for (const row of rows) {\n try {\n await STORAGE.deleteFile((row as any).path)\n } catch {\n /* already gone from storage */\n }\n }\n if (rows.length) await getRepository(Attachment).delete(rows.map(row => (row as any).id))\n}\n\n/** Removes a conversation's files from storage and their rows - the purge's part (§9-5). */\nexport async function removeSessionFiles(session: { id?: string; domain?: { id?: string } }): Promise<void> {\n const rows = await getRepository(Attachment).find({ where: { refType: CHAT_ATTACHMENT_REF, refBy: session.id } as any })\n for (const row of rows) {\n try {\n await STORAGE.deleteFile((row as any).path)\n } catch {\n /* A file already gone from storage must not keep its row alive. */\n }\n }\n if (rows.length) await getRepository(Attachment).delete(rows.map(row => (row as any).id))\n}\n"]}
1
+ {"version":3,"file":"session-attachments.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * A conversation's files - stored in attachment-base, opened only through the conversation.\n *\n * Files are kept with `refType: 'chat-session'`, `refBy: <session id>` (design/ai/shared-dock.md §9-2).\n * They are never handed out through attachment-base's own route: that route is public and unauthenticated\n * (routes.ts, \"TODO make this secure\"), so a private conversation's file would leave the conversation.\n * Every read here passes the conversation's gate first (§9-3); this module only does the storage part.\n */\nimport { Attachment, STORAGE, createAttachment } from '@things-factory/attachment-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { attachmentKindOf, TEXT_CHARS_FOR_MODEL } from './attachment-policy.js'\n\nexport const CHAT_ATTACHMENT_REF = 'chat-session'\n\nexport interface StoredChatAttachment {\n id: string\n name: string\n mimetype: string\n size: number\n}\n\nexport function describeAttachment(row: any): StoredChatAttachment {\n return { id: row.id, name: row.name, mimetype: row.mimetype, size: Number(row.size) || 0 }\n}\n\n/** Stores uploaded files under the conversation. The caller has passed the write gate and the mode's policy. */\nexport async function saveChatAttachments(sessionId: string, files: any[], context: any): Promise<StoredChatAttachment[]> {\n const saved: StoredChatAttachment[] = []\n for (const file of files) {\n const row = await createAttachment(null, { attachment: { file, category: 'chat', refType: CHAT_ATTACHMENT_REF, refBy: sessionId } }, context)\n saved.push(describeAttachment(row))\n }\n return saved\n}\n\n/** One file of the conversation - `undefined` when it is not this conversation's. */\nexport async function findChatAttachment(domainId: string, sessionId: string, attachmentId: string): Promise<any | undefined> {\n return (\n (await getRepository(Attachment).findOne({\n where: { id: attachmentId, domain: { id: domainId }, refType: CHAT_ATTACHMENT_REF, refBy: sessionId } as any\n })) ?? undefined\n )\n}\n\n/**\n * What the model gets for a file: an image as bytes, a text file as its text (cut at the mode's maxChars -\n * TEXT_CHARS_FOR_MODEL when it declares none -\n * and the cut is said), anything else as a line that names it.\n */\nexport async function attachmentForModel(row: any, maxChars: number = TEXT_CHARS_FOR_MODEL): Promise<\n | { type: 'image'; data: Buffer; mediaType: string; name: string }\n | { type: 'text'; text: string; name: string; truncated: boolean }\n> {\n const kind = attachmentKindOf({ type: row.mimetype, name: row.name })\n if (kind === 'image') {\n const data = await STORAGE.readFile(row.path)\n return { type: 'image', data: Buffer.isBuffer(data) ? data : Buffer.from(data), mediaType: row.mimetype, name: row.name }\n }\n const content = String(await STORAGE.readFile(row.path, 'utf8'))\n const truncated = content.length > maxChars\n return { type: 'text', text: truncated ? content.slice(0, maxChars) : content, name: row.name, truncated }\n}\n\n/** Removes these files from storage and their rows. */\nexport async function removeAttachmentRows(ids: string[]): Promise<void> {\n if (!ids.length) return\n const rows = await getRepository(Attachment).find({ where: ids.map(id => ({ id, refType: CHAT_ATTACHMENT_REF })) as any })\n for (const row of rows) {\n try {\n await STORAGE.deleteFile((row as any).path)\n } catch {\n /* already gone from storage */\n }\n }\n if (rows.length) await getRepository(Attachment).delete(rows.map(row => (row as any).id))\n}\n\n/** Removes a conversation's files from storage and their rows - the purge's part (§9-5). */\nexport async function removeSessionFiles(session: { id?: string; domain?: { id?: string } }): Promise<void> {\n const rows = await getRepository(Attachment).find({ where: { refType: CHAT_ATTACHMENT_REF, refBy: session.id } as any })\n for (const row of rows) {\n try {\n await STORAGE.deleteFile((row as any).path)\n } catch {\n /* A file already gone from storage must not keep its row alive. */\n }\n }\n if (rows.length) await getRepository(Attachment).delete(rows.map(row => (row as any).id))\n}\n"]}
@@ -36,6 +36,8 @@ export interface EntryApplyDeps {
36
36
  }>;
37
37
  state: any;
38
38
  report?(message: string, error: unknown): void;
39
+ /** The fingerprint of what the AI 작업판 showed, when the person reviewed it there - entered only if the proposal is still that one. */
40
+ reviewedHash?: string;
39
41
  }
40
42
  export declare function findEntryProposal(messages: StoredProposalMessage[], key: string): {
41
43
  proposal?: any;
@@ -18,6 +18,7 @@ exports.entryProposalAnswered = entryProposalAnswered;
18
18
  const proposal_outcome_js_1 = require("../chat-message/proposal-outcome.js");
19
19
  const entry_update_js_1 = require("./entry-update.js");
20
20
  const entry_target_js_1 = require("./entry-target.js");
21
+ const entry_hash_js_1 = require("./entry-hash.js");
21
22
  const nothing = (verdict, errorCode) => ({ verdict, errorCode, applied: 0, missed: 0, rows: [] });
22
23
  function findEntryProposal(messages, key) {
23
24
  let at = -1;
@@ -45,6 +46,9 @@ async function applyEntryProposal(key, deps, targetOf = entry_target_js_1.entryT
45
46
  return nothing('refused', 'superseded');
46
47
  if (deps.answered)
47
48
  return nothing('refused', 'already-answered');
49
+ /* What the 작업판 showed and what is entered are one proposal (design/ai/workspace.md §6-1). */
50
+ if (deps.reviewedHash && deps.reviewedHash !== (0, entry_hash_js_1.entryProposalHash)(proposal))
51
+ return nothing('refused', 'review-stale');
48
52
  const target = targetOf(proposal.target);
49
53
  if (!target)
50
54
  return nothing('unavailable', 'unknown-entry-target');
@@ -1 +1 @@
1
- {"version":3,"file":"entry-apply.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-apply.ts"],"names":[],"mappings":";;AAuDA,8CAYC;AAED,gDAgFC;AAGD,sDAQC;AAhKD;;;;;;;;;;;GAWG;AACH,6EAAgG;AAChG,uDAAkG;AAClG,uDAA+P;AAuC/P,MAAM,OAAO,GAAG,CAAC,OAAqC,EAAE,SAAiB,EAAqB,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;AAE1J,SAAgB,iBAAiB,CAAC,QAAiC,EAAE,GAAW;IAC9E,IAAI,EAAE,GAAG,CAAC,CAAC,CAAA;IACX,IAAI,QAAa,CAAA;IACjB,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QAC9B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,SAAS,IAAI,EAAE;YAAE,IAAI,GAAG,EAAE,GAAG,KAAK,GAAG;gBAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAA;IAC7F,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAA;IAC3C,4HAA4H;IAC5H,MAAM,UAAU,GAAG,QAAQ;SACxB,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;SACb,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,OAAO,IAAI,GAAG,EAAE,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC1K,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;AACjC,CAAC;AAEM,KAAK,UAAU,kBAAkB,CAAC,GAAW,EAAE,IAAoB,EAAE,WAAqD,+BAAa;IAC5I,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;IACtE,IAAI,CAAC,QAAQ;QAAE,OAAO,OAAO,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAA;IAC9D,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC,SAAS,EAAE,uBAAuB,CAAC,CAAA;IACjF,IAAI,UAAU;QAAE,OAAO,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IACvD,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,OAAO,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAA;IAEhE,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC,aAAa,EAAE,sBAAsB,CAAC,CAAA;IAClE,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;IAC9E,MAAM,IAAI,GAAG,IAAA,6BAAW,EAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACzC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACnC,MAAM,IAAA,kCAAgB,EAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAA,+BAAa,EAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QAClL,OAAO,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;IAC1C,CAAC;IAED,sGAAsG;IACtG,IAAI,EAAE,IAAI,EAAE,GAAG,IAAA,oCAAkB,EAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAa,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;IACvI,4FAA4F;IAC5F,IAAI,GAAG,MAAM,IAAA,mCAAiB,EAAC,MAAM,CAAC,MAAM,EAAE,IAAA,gCAAc,EAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9H,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,IAAA,mCAAiB,EAAC,IAAI,EAAE,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QACjF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,CAAC,kCAAkC,MAAM,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,CAAA;YAC5E,OAAO,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAA;IACpC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,+GAA+G;QAC/G,KAAK,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,IAAA,6BAAW,EAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,2BAAS,CAAC,CAAA;YACvC,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAA;gBACvD,KAAK,MAAM,GAAG,IAAI,OAAO;oBACvB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,uBAAuB,OAAO,CAAC,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC,CAAA;gBACpL,SAAQ;YACV,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;gBACnF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;gBAC/G,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;gBAClF,KAAK,MAAM,GAAG,IAAI,OAAO;oBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;YAC3N,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACpB,KAAK,MAAM,GAAG,IAAI,OAAO;oBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAA;YAC9G,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;IAC3C,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,IAAA,2BAAS,EAAC,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAA;YACnD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;YAChH,SAAQ;QACV,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAU,CAAC,GAAG,CAAC,CAAC,CAAA;YAC1E,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC,CAAA;gBACnH,SAAQ;YACV,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;YACpD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;QAC/I,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAA;QACnF,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,CAAA;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAA;IACvC,MAAM,IAAA,kCAAgB,EACpB,MAAM,EACN,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAA,+BAAa,EAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EACzM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CACtB,CAAA;IACD,OAAO,OAAO;QACZ,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;QACzD,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AACzF,CAAC;AAED,0HAA0H;AAC1H,SAAgB,qBAAqB,CAAC,GAAW,EAAE,KAA6C;IAC9F,MAAM,QAAQ,GAAG,GAAG,6CAAuB,GAAG,GAAG,IAAI,CAAA;IACrD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACvB,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAChC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAA;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACxC,OAAO,uCAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC,CAAA;IAC9F,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,QAAa,EAAE,MAAmB,EAAE,IAAoB;IAC/F,MAAM,IAAI,GAAG,IAAA,gCAAc,EAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;IAC5B,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAAA;IACpE,KAAK,MAAM,IAAI,IAAI,CAAC,IAAA,6BAAW,EAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,IAAA,6BAAW,EAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;QACvG,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACnC,MAAM,IAAA,kCAAgB,EAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;YACvJ,OAAO,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAgB,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAA;IAC/C,IAAI,GAAG,CAAA;IACP,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,IAAA,6BAAW,EAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,MAAM,EAAE,CAAC,2BAA2B,MAAM,CAAC,GAAG,gBAAgB,EAAE,KAAK,CAAC,CAAA;QAC3E,OAAO,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAA;IAC1C,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAA;IACpC,oHAAoH;IACpH,KAAK,MAAM,GAAG,IAAI,MAAM,IAAA,kCAAgB,EAAC,MAAM,EAAE,IAAA,iCAAe,EAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;QACnH,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAA;YACnD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;YAChH,SAAQ;QACV,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,GAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;YAC1H,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC,CAAA;gBACnH,SAAQ;YACV,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;QACtG,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAA;QACnF,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,CAAA;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAA;IACvC,MAAM,IAAA,kCAAgB,EACpB,MAAM,EACN,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAC5L,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CACtB,CAAA;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAChK,CAAC","sourcesContent":["/*\n * Writing a proposal the person pressed (operato-application/design/ai/data-entry.md §4).\n *\n * The rows written are the **stored** proposal's, never rows the screen sends: what the person reviewed\n * on the card is what goes in. The server asks everything again - the door, whether a newer card replaced\n * this one, whether it was already answered, the target's checks (records move between proposing and\n * pressing) - and enters each row by running the application's own mutation with the person's request\n * state. Each call is its own request (the door's `@transaction`), so one refused row does not undo the\n * others and each is reported by its place.\n *\n * Dependencies come in as arguments so the rules are tested without a database.\n */\nimport { PROPOSAL_OUTCOME_PREFIX, PROPOSAL_VERDICTS } from '../chat-message/proposal-outcome.js'\nimport { readRecords, stillAsProposed, withChangeChecks, type ChangeRow } from './entry-update.js'\nimport { changeToolName, zonedDateTimes, entryGroups, resolveReferences, entryDoorOf, entryTargetOf, entryToolName, rootFieldOf, isBlocked, recordEntryEvent, normalizeEntryRows, withCheckedIssues, type EntryRow, type EntryTarget } from './entry-target.js'\n\nexport interface StoredProposalMessage {\n id: string\n proposals: any[]\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n /** A blocking issue kept it out, or the target refused it. */\n error?: string\n}\n\nexport interface EntryApplyOutcome {\n verdict: 'accepted' | 'refused' | 'unavailable'\n errorCode?: string\n applied: number\n missed: number\n rows: EntryRowResult[]\n}\n\nexport interface EntryApplyDeps {\n /** The conversation's messages that carry proposals, oldest first. */\n messages: StoredProposalMessage[]\n /** Whether an outcome line already answers this key. */\n answered: boolean\n passesDoor(door: string): Promise<boolean>\n /**\n * Runs one of the application's queries or mutations with the person's request state - its API, its directives\n * and its own checks. Answers what GraphQL answers.\n */\n execute(document: string, variables: Record<string, unknown>): Promise<{ data?: any; errors?: readonly { message: string }[] }>\n state: any\n report?(message: string, error: unknown): void\n}\n\nconst nothing = (verdict: EntryApplyOutcome['verdict'], errorCode: string): EntryApplyOutcome => ({ verdict, errorCode, applied: 0, missed: 0, rows: [] })\n\nexport function findEntryProposal(messages: StoredProposalMessage[], key: string): { proposal?: any; superseded: boolean } {\n let at = -1\n let proposal: any\n messages.forEach((message, i) => {\n for (const one of message.proposals ?? []) if (one?.key === key) (at = i), (proposal = one)\n })\n if (!proposal) return { superseded: false }\n /* A later card of the same record and the same work replaces this one - pressing both would do the corrected work twice. */\n const superseded = messages\n .slice(at + 1)\n .some(message => (message.proposals ?? []).some(one => one?.kind === 'entry' && one?.target === proposal.target && (one?.op ?? 'create') === (proposal.op ?? 'create')))\n return { proposal, superseded }\n}\n\nexport async function applyEntryProposal(key: string, deps: EntryApplyDeps, targetOf: (key: string) => EntryTarget | undefined = entryTargetOf): Promise<EntryApplyOutcome> {\n const { proposal, superseded } = findEntryProposal(deps.messages, key)\n if (!proposal) return nothing('refused', 'proposal-not-found')\n if (proposal.kind !== 'entry') return nothing('refused', 'not-an-entry-proposal')\n if (superseded) return nothing('refused', 'superseded')\n if (deps.answered) return nothing('refused', 'already-answered')\n\n const target = targetOf(proposal.target)\n if (!target) return nothing('unavailable', 'unknown-entry-target')\n if (proposal.op === 'update') return applyChanges(key, proposal, target, deps)\n const door = entryDoorOf(target.mutation)\n if (!(await deps.passesDoor(door))) {\n await recordEntryEvent(target, { phase: 'apply', tool: entryToolName(target.key), rows: 0, ok: false, error: 'door-closed', detail: { proposalKey: key } }, { state: deps.state })\n return nothing('refused', 'door-closed')\n }\n\n /* Read again from the stored values - the same rules, and today's records for the target's checks. */\n let { rows } = normalizeEntryRows(target, (proposal.rows ?? []).map((row: EntryRow) => ({ values: row.values, sources: row.sources })))\n /* A reference that was not settled on the card is asked again - never entered as a name. */\n rows = await resolveReferences(target.fields, zonedDateTimes(target.fields, rows, deps.state?.domain?.timezone), deps.execute)\n if (target.check) {\n try {\n rows = withCheckedIssues(rows, await target.check(rows, { state: deps.state }))\n } catch (error) {\n deps.report?.(`[ai-assistant] entry check of '${target.key}' failed`, error)\n return nothing('refused', 'check-failed')\n }\n }\n\n const results: EntryRowResult[] = []\n if (target.group) {\n /* One call per group; a group with a row kept out stays out whole - an order missing a line is not entered. */\n for (const [, members] of entryGroups(rows, target.group.by)) {\n const blocked = members.find(isBlocked)\n if (blocked) {\n const issue = blocked.issues.find(one => one.blocking)!\n for (const row of members)\n results.push({ index: row.index, ok: false, error: row === blocked ? (issue.field ? `${issue.field}: ${issue.code}` : issue.code) : `group-kept-out (row ${blocked.index + 1})` })\n continue\n }\n try {\n const answer = await deps.execute(target.mutation, target.group.variables(members))\n const error = answer.errors?.length ? answer.errors.map(one => one.message).join('; ') || 'refused' : undefined\n const applied = error ? undefined : target.group.appliedOf?.(answer.data, members)\n for (const row of members) results.push(error ? { index: row.index, ok: false, error } : { index: row.index, ok: true, ...(applied?.id ? { id: applied.id } : {}), ...(applied?.label ? { label: applied.label } : {}) })\n } catch (error: any) {\n for (const row of members) results.push({ index: row.index, ok: false, error: error?.message || 'refused' })\n }\n }\n results.sort((a, b) => a.index - b.index)\n }\n for (const row of target.group ? [] : rows) {\n if (isBlocked(row)) {\n const issue = row.issues.find(one => one.blocking)!\n results.push({ index: row.index, ok: false, error: issue.field ? `${issue.field}: ${issue.code}` : issue.code })\n continue\n }\n try {\n const answer = await deps.execute(target.mutation, target.variables!(row))\n if (answer.errors?.length) {\n results.push({ index: row.index, ok: false, error: answer.errors.map(one => one.message).join('; ') || 'refused' })\n continue\n }\n const applied = target.appliedOf?.(answer.data, row)\n results.push({ index: row.index, ok: true, ...(applied?.id ? { id: applied.id } : {}), ...(applied?.label ? { label: applied.label } : {}) })\n } catch (error: any) {\n results.push({ index: row.index, ok: false, error: error?.message || 'refused' })\n }\n }\n\n const applied = results.filter(one => one.ok).length\n const missed = results.length - applied\n await recordEntryEvent(\n target,\n { phase: 'apply', tool: entryToolName(target.key), rows: results.length, ok: applied > 0, applied, missed, ...(applied ? {} : { error: 'no-row-entered' }), detail: { proposalKey: key, rows: results } },\n { state: deps.state }\n )\n return applied\n ? { verdict: 'accepted', applied, missed, rows: results }\n : { verdict: 'refused', errorCode: 'no-row-entered', applied, missed, rows: results }\n}\n\n/** An outcome line already answers this key - `requested` alone does not: the dock writes it just before calling here. */\nexport function entryProposalAnswered(key: string, lines: readonly (string | null | undefined)[]): boolean {\n const answered = `${PROPOSAL_OUTCOME_PREFIX}${key}: `\n return lines.some(line => {\n const text = (line ?? '').trim()\n if (!text.startsWith(answered)) return false\n const rest = text.slice(answered.length)\n return PROPOSAL_VERDICTS.some(verdict => rest === verdict || rest.startsWith(`${verdict} `))\n })\n}\n\n/**\n * A pressed 수정안: the doors again, each record read again as the person, a field that moved since the card was\n * made keeps its row out (`changed-since`), and each record is changed by the application's own mutation.\n */\nasync function applyChanges(key: string, proposal: any, target: EntryTarget, deps: EntryApplyDeps): Promise<EntryApplyOutcome> {\n const tool = changeToolName(target.key)\n const update = target.update\n if (!update) return nothing('unavailable', 'target-does-not-change')\n for (const door of [rootFieldOf(update.mutation, 'mutation'), rootFieldOf(update.read.query, 'query')]) {\n if (!(await deps.passesDoor(door))) {\n await recordEntryEvent(target, { phase: 'apply', tool, rows: 0, ok: false, error: 'door-closed', detail: { proposalKey: key } }, { state: deps.state })\n return nothing('refused', 'door-closed')\n }\n }\n\n const stored: ChangeRow[] = proposal.rows ?? []\n let now\n try {\n now = await readRecords(target, stored.map(row => row.id), deps.execute)\n } catch (error) {\n deps.report?.(`[ai-assistant] reading '${target.key}' again failed`, error)\n return nothing('refused', 'read-failed')\n }\n\n const results: EntryRowResult[] = []\n /* The target's checks of the changes again - another record may have taken the new name since the card was made. */\n for (const row of await withChangeChecks(target, stillAsProposed(target, stored, now), now, { state: deps.state })) {\n if (row.issues.some(one => one.blocking)) {\n const issue = row.issues.find(one => one.blocking)!\n results.push({ index: row.index, ok: false, error: issue.field ? `${issue.field}: ${issue.code}` : issue.code })\n continue\n }\n try {\n const answer = await deps.execute(update.mutation, update.variables({ id: row.id, record: row.now!, values: row.values }))\n if (answer.errors?.length) {\n results.push({ index: row.index, ok: false, error: answer.errors.map(one => one.message).join('; ') || 'refused' })\n continue\n }\n results.push({ index: row.index, ok: true, id: row.id, ...(row.label ? { label: row.label } : {}) })\n } catch (error: any) {\n results.push({ index: row.index, ok: false, error: error?.message || 'refused' })\n }\n }\n\n const applied = results.filter(one => one.ok).length\n const missed = results.length - applied\n await recordEntryEvent(\n target,\n { phase: 'apply', tool, rows: results.length, ok: applied > 0, applied, missed, ...(applied ? {} : { error: 'no-row-changed' }), detail: { proposalKey: key, op: 'update', rows: results } },\n { state: deps.state }\n )\n return applied ? { verdict: 'accepted', applied, missed, rows: results } : { verdict: 'refused', errorCode: 'no-row-changed', applied, missed, rows: results }\n}\n"]}
1
+ {"version":3,"file":"entry-apply.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-apply.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Writing a proposal the person pressed (operato-application/design/ai/data-entry.md §4).\n *\n * The rows written are the **stored** proposal's, never rows the screen sends: what the person reviewed\n * on the card is what goes in. The server asks everything again - the door, whether a newer card replaced\n * this one, whether it was already answered, the target's checks (records move between proposing and\n * pressing) - and enters each row by running the application's own mutation with the person's request\n * state. Each call is its own request (the door's `@transaction`), so one refused row does not undo the\n * others and each is reported by its place.\n *\n * Dependencies come in as arguments so the rules are tested without a database.\n */\nimport { PROPOSAL_OUTCOME_PREFIX, PROPOSAL_VERDICTS } from '../chat-message/proposal-outcome.js'\nimport { readRecords, stillAsProposed, withChangeChecks, type ChangeRow } from './entry-update.js'\nimport { changeToolName, zonedDateTimes, entryGroups, resolveReferences, entryDoorOf, entryTargetOf, entryToolName, rootFieldOf, isBlocked, recordEntryEvent, normalizeEntryRows, withCheckedIssues, type EntryRow, type EntryTarget } from './entry-target.js'\nimport { entryProposalHash } from './entry-hash.js'\n\nexport interface StoredProposalMessage {\n id: string\n proposals: any[]\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n /** A blocking issue kept it out, or the target refused it. */\n error?: string\n}\n\nexport interface EntryApplyOutcome {\n verdict: 'accepted' | 'refused' | 'unavailable'\n errorCode?: string\n applied: number\n missed: number\n rows: EntryRowResult[]\n}\n\nexport interface EntryApplyDeps {\n /** The conversation's messages that carry proposals, oldest first. */\n messages: StoredProposalMessage[]\n /** Whether an outcome line already answers this key. */\n answered: boolean\n passesDoor(door: string): Promise<boolean>\n /**\n * Runs one of the application's queries or mutations with the person's request state - its API, its directives\n * and its own checks. Answers what GraphQL answers.\n */\n execute(document: string, variables: Record<string, unknown>): Promise<{ data?: any; errors?: readonly { message: string }[] }>\n state: any\n report?(message: string, error: unknown): void\n /** The fingerprint of what the AI 작업판 showed, when the person reviewed it there - entered only if the proposal is still that one. */\n reviewedHash?: string\n}\n\nconst nothing = (verdict: EntryApplyOutcome['verdict'], errorCode: string): EntryApplyOutcome => ({ verdict, errorCode, applied: 0, missed: 0, rows: [] })\n\nexport function findEntryProposal(messages: StoredProposalMessage[], key: string): { proposal?: any; superseded: boolean } {\n let at = -1\n let proposal: any\n messages.forEach((message, i) => {\n for (const one of message.proposals ?? []) if (one?.key === key) (at = i), (proposal = one)\n })\n if (!proposal) return { superseded: false }\n /* A later card of the same record and the same work replaces this one - pressing both would do the corrected work twice. */\n const superseded = messages\n .slice(at + 1)\n .some(message => (message.proposals ?? []).some(one => one?.kind === 'entry' && one?.target === proposal.target && (one?.op ?? 'create') === (proposal.op ?? 'create')))\n return { proposal, superseded }\n}\n\nexport async function applyEntryProposal(key: string, deps: EntryApplyDeps, targetOf: (key: string) => EntryTarget | undefined = entryTargetOf): Promise<EntryApplyOutcome> {\n const { proposal, superseded } = findEntryProposal(deps.messages, key)\n if (!proposal) return nothing('refused', 'proposal-not-found')\n if (proposal.kind !== 'entry') return nothing('refused', 'not-an-entry-proposal')\n if (superseded) return nothing('refused', 'superseded')\n if (deps.answered) return nothing('refused', 'already-answered')\n /* What the 작업판 showed and what is entered are one proposal (design/ai/workspace.md §6-1). */\n if (deps.reviewedHash && deps.reviewedHash !== entryProposalHash(proposal)) return nothing('refused', 'review-stale')\n\n const target = targetOf(proposal.target)\n if (!target) return nothing('unavailable', 'unknown-entry-target')\n if (proposal.op === 'update') return applyChanges(key, proposal, target, deps)\n const door = entryDoorOf(target.mutation)\n if (!(await deps.passesDoor(door))) {\n await recordEntryEvent(target, { phase: 'apply', tool: entryToolName(target.key), rows: 0, ok: false, error: 'door-closed', detail: { proposalKey: key } }, { state: deps.state })\n return nothing('refused', 'door-closed')\n }\n\n /* Read again from the stored values - the same rules, and today's records for the target's checks. */\n let { rows } = normalizeEntryRows(target, (proposal.rows ?? []).map((row: EntryRow) => ({ values: row.values, sources: row.sources })))\n /* A reference that was not settled on the card is asked again - never entered as a name. */\n rows = await resolveReferences(target.fields, zonedDateTimes(target.fields, rows, deps.state?.domain?.timezone), deps.execute)\n if (target.check) {\n try {\n rows = withCheckedIssues(rows, await target.check(rows, { state: deps.state }))\n } catch (error) {\n deps.report?.(`[ai-assistant] entry check of '${target.key}' failed`, error)\n return nothing('refused', 'check-failed')\n }\n }\n\n const results: EntryRowResult[] = []\n if (target.group) {\n /* One call per group; a group with a row kept out stays out whole - an order missing a line is not entered. */\n for (const [, members] of entryGroups(rows, target.group.by)) {\n const blocked = members.find(isBlocked)\n if (blocked) {\n const issue = blocked.issues.find(one => one.blocking)!\n for (const row of members)\n results.push({ index: row.index, ok: false, error: row === blocked ? (issue.field ? `${issue.field}: ${issue.code}` : issue.code) : `group-kept-out (row ${blocked.index + 1})` })\n continue\n }\n try {\n const answer = await deps.execute(target.mutation, target.group.variables(members))\n const error = answer.errors?.length ? answer.errors.map(one => one.message).join('; ') || 'refused' : undefined\n const applied = error ? undefined : target.group.appliedOf?.(answer.data, members)\n for (const row of members) results.push(error ? { index: row.index, ok: false, error } : { index: row.index, ok: true, ...(applied?.id ? { id: applied.id } : {}), ...(applied?.label ? { label: applied.label } : {}) })\n } catch (error: any) {\n for (const row of members) results.push({ index: row.index, ok: false, error: error?.message || 'refused' })\n }\n }\n results.sort((a, b) => a.index - b.index)\n }\n for (const row of target.group ? [] : rows) {\n if (isBlocked(row)) {\n const issue = row.issues.find(one => one.blocking)!\n results.push({ index: row.index, ok: false, error: issue.field ? `${issue.field}: ${issue.code}` : issue.code })\n continue\n }\n try {\n const answer = await deps.execute(target.mutation, target.variables!(row))\n if (answer.errors?.length) {\n results.push({ index: row.index, ok: false, error: answer.errors.map(one => one.message).join('; ') || 'refused' })\n continue\n }\n const applied = target.appliedOf?.(answer.data, row)\n results.push({ index: row.index, ok: true, ...(applied?.id ? { id: applied.id } : {}), ...(applied?.label ? { label: applied.label } : {}) })\n } catch (error: any) {\n results.push({ index: row.index, ok: false, error: error?.message || 'refused' })\n }\n }\n\n const applied = results.filter(one => one.ok).length\n const missed = results.length - applied\n await recordEntryEvent(\n target,\n { phase: 'apply', tool: entryToolName(target.key), rows: results.length, ok: applied > 0, applied, missed, ...(applied ? {} : { error: 'no-row-entered' }), detail: { proposalKey: key, rows: results } },\n { state: deps.state }\n )\n return applied\n ? { verdict: 'accepted', applied, missed, rows: results }\n : { verdict: 'refused', errorCode: 'no-row-entered', applied, missed, rows: results }\n}\n\n/** An outcome line already answers this key - `requested` alone does not: the dock writes it just before calling here. */\nexport function entryProposalAnswered(key: string, lines: readonly (string | null | undefined)[]): boolean {\n const answered = `${PROPOSAL_OUTCOME_PREFIX}${key}: `\n return lines.some(line => {\n const text = (line ?? '').trim()\n if (!text.startsWith(answered)) return false\n const rest = text.slice(answered.length)\n return PROPOSAL_VERDICTS.some(verdict => rest === verdict || rest.startsWith(`${verdict} `))\n })\n}\n\n/**\n * A pressed 수정안: the doors again, each record read again as the person, a field that moved since the card was\n * made keeps its row out (`changed-since`), and each record is changed by the application's own mutation.\n */\nasync function applyChanges(key: string, proposal: any, target: EntryTarget, deps: EntryApplyDeps): Promise<EntryApplyOutcome> {\n const tool = changeToolName(target.key)\n const update = target.update\n if (!update) return nothing('unavailable', 'target-does-not-change')\n for (const door of [rootFieldOf(update.mutation, 'mutation'), rootFieldOf(update.read.query, 'query')]) {\n if (!(await deps.passesDoor(door))) {\n await recordEntryEvent(target, { phase: 'apply', tool, rows: 0, ok: false, error: 'door-closed', detail: { proposalKey: key } }, { state: deps.state })\n return nothing('refused', 'door-closed')\n }\n }\n\n const stored: ChangeRow[] = proposal.rows ?? []\n let now\n try {\n now = await readRecords(target, stored.map(row => row.id), deps.execute)\n } catch (error) {\n deps.report?.(`[ai-assistant] reading '${target.key}' again failed`, error)\n return nothing('refused', 'read-failed')\n }\n\n const results: EntryRowResult[] = []\n /* The target's checks of the changes again - another record may have taken the new name since the card was made. */\n for (const row of await withChangeChecks(target, stillAsProposed(target, stored, now), now, { state: deps.state })) {\n if (row.issues.some(one => one.blocking)) {\n const issue = row.issues.find(one => one.blocking)!\n results.push({ index: row.index, ok: false, error: issue.field ? `${issue.field}: ${issue.code}` : issue.code })\n continue\n }\n try {\n const answer = await deps.execute(update.mutation, update.variables({ id: row.id, record: row.now!, values: row.values }))\n if (answer.errors?.length) {\n results.push({ index: row.index, ok: false, error: answer.errors.map(one => one.message).join('; ') || 'refused' })\n continue\n }\n results.push({ index: row.index, ok: true, id: row.id, ...(row.label ? { label: row.label } : {}) })\n } catch (error: any) {\n results.push({ index: row.index, ok: false, error: error?.message || 'refused' })\n }\n }\n\n const applied = results.filter(one => one.ok).length\n const missed = results.length - applied\n await recordEntryEvent(\n target,\n { phase: 'apply', tool, rows: results.length, ok: applied > 0, applied, missed, ...(applied ? {} : { error: 'no-row-changed' }), detail: { proposalKey: key, op: 'update', rows: results } },\n { state: deps.state }\n )\n return applied ? { verdict: 'accepted', applied, missed, rows: results } : { verdict: 'refused', errorCode: 'no-row-changed', applied, missed, rows: results }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ /** FNV-1a over the canonical text - a fingerprint to compare, not a secret. */
2
+ export declare function entryProposalHash(proposal: any): string;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /*
3
+ * One 입력안's fingerprint - what the AI 작업판 showed and what the card enters are the same proposal
4
+ * (operato-application/design/ai/workspace.md §6-1). The screen's copy and the server's are one rule;
5
+ * `test/entry-hash-agree.test.ts` runs both over one table.
6
+ *
7
+ * It covers what is entered: the key, the target, create or change, and each row's index, record and values.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.entryProposalHash = entryProposalHash;
11
+ const canonical = (value) => {
12
+ if (value === null || value === undefined)
13
+ return 'null';
14
+ if (Array.isArray(value))
15
+ return `[${value.map(canonical).join(',')}]`;
16
+ if (typeof value === 'object')
17
+ return `{${Object.keys(value)
18
+ .filter(name => value[name] !== undefined)
19
+ .sort()
20
+ .map(name => `${JSON.stringify(name)}:${canonical(value[name])}`)
21
+ .join(',')}}`;
22
+ return JSON.stringify(value);
23
+ };
24
+ /** FNV-1a over the canonical text - a fingerprint to compare, not a secret. */
25
+ function entryProposalHash(proposal) {
26
+ const text = canonical({
27
+ key: proposal?.key,
28
+ target: proposal?.target,
29
+ op: proposal?.op ?? 'create',
30
+ rows: (proposal?.rows ?? []).map((row) => ({ index: row?.index, id: row?.id, values: row?.values ?? {} }))
31
+ });
32
+ let hash = 0x811c9dc5;
33
+ for (let i = 0; i < text.length; i++) {
34
+ hash ^= text.charCodeAt(i);
35
+ hash = Math.imul(hash, 0x01000193) >>> 0;
36
+ }
37
+ return `${hash.toString(16).padStart(8, '0')}-${text.length.toString(16)}`;
38
+ }
39
+ //# sourceMappingURL=entry-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-hash.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-hash.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAeH,8CAaC;AA1BD,MAAM,SAAS,GAAG,CAAC,KAAc,EAAU,EAAE;IAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAC3B,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC;aACpC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAE,KAAa,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC;aAClD,IAAI,EAAE;aACN,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,CAAE,KAAa,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;aACzE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;IACjB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAA;AAED,+EAA+E;AAC/E,SAAgB,iBAAiB,CAAC,QAAa;IAC7C,MAAM,IAAI,GAAG,SAAS,CAAC;QACrB,GAAG,EAAE,QAAQ,EAAE,GAAG;QAClB,MAAM,EAAE,QAAQ,EAAE,MAAM;QACxB,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,QAAQ;QAC5B,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;KAChH,CAAC,CAAA;IACF,IAAI,IAAI,GAAG,UAAU,CAAA;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC1B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAA;AAC5E,CAAC","sourcesContent":["/*\n * One 입력안's fingerprint - what the AI 작업판 showed and what the card enters are the same proposal\n * (operato-application/design/ai/workspace.md §6-1). The screen's copy and the server's are one rule;\n * `test/entry-hash-agree.test.ts` runs both over one table.\n *\n * It covers what is entered: the key, the target, create or change, and each row's index, record and values.\n */\n\nconst canonical = (value: unknown): string => {\n if (value === null || value === undefined) return 'null'\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`\n if (typeof value === 'object')\n return `{${Object.keys(value as object)\n .filter(name => (value as any)[name] !== undefined)\n .sort()\n .map(name => `${JSON.stringify(name)}:${canonical((value as any)[name])}`)\n .join(',')}}`\n return JSON.stringify(value)\n}\n\n/** FNV-1a over the canonical text - a fingerprint to compare, not a secret. */\nexport function entryProposalHash(proposal: any): string {\n const text = canonical({\n key: proposal?.key,\n target: proposal?.target,\n op: proposal?.op ?? 'create',\n rows: (proposal?.rows ?? []).map((row: any) => ({ index: row?.index, id: row?.id, values: row?.values ?? {} }))\n })\n let hash = 0x811c9dc5\n for (let i = 0; i < text.length; i++) {\n hash ^= text.charCodeAt(i)\n hash = Math.imul(hash, 0x01000193) >>> 0\n }\n return `${hash.toString(16).padStart(8, '0')}-${text.length.toString(16)}`\n}\n"]}
@@ -1,4 +1,4 @@
1
1
  import { type EntryApplyOutcome } from './entry-apply.js';
2
2
  export declare class EntryProposalResolver {
3
- applyEntryProposal(sessionId: string, proposalKey: string, context: ResolverContext): Promise<EntryApplyOutcome>;
3
+ applyEntryProposal(sessionId: string, proposalKey: string, context: ResolverContext, reviewedHash?: string): Promise<EntryApplyOutcome>;
4
4
  }
@@ -20,7 +20,7 @@ let EntryProposalResolver = class EntryProposalResolver {
20
20
  * Not `@transaction`: each row is its own call of the application's mutation (entry-apply.ts), so one
21
21
  * refused row keeps the others. The dock reports the outcome afterwards through `reportProposalOutcome`.
22
22
  */
23
- async applyEntryProposal(sessionId, proposalKey, context) {
23
+ async applyEntryProposal(sessionId, proposalKey, context, reviewedHash) {
24
24
  const { domain, user } = context.state;
25
25
  const session = await (0, shell_1.getRepository)(chat_session_js_1.ChatSession).findOneBy({ id: sessionId, domain: { id: domain.id } });
26
26
  if (!session)
@@ -53,7 +53,8 @@ let EntryProposalResolver = class EntryProposalResolver {
53
53
  * door opens its own transaction per row (entry-tools.ts `runAsCaller`).
54
54
  */
55
55
  execute: (0, entry_tools_js_1.runAsCaller)(context),
56
- report: (message, error) => env_1.logger.error(message, error)
56
+ report: (message, error) => env_1.logger.error(message, error),
57
+ reviewedHash: reviewedHash || undefined
57
58
  });
58
59
  }
59
60
  };
@@ -66,8 +67,9 @@ tslib_1.__decorate([
66
67
  tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
67
68
  tslib_1.__param(1, (0, type_graphql_1.Arg)('proposalKey')),
68
69
  tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
70
+ tslib_1.__param(3, (0, type_graphql_1.Arg)('reviewedHash', { nullable: true, description: 'The fingerprint of the proposal as the AI workspace showed it; a proposal that is no longer that one is refused.' })),
69
71
  tslib_1.__metadata("design:type", Function),
70
- tslib_1.__metadata("design:paramtypes", [String, String, Object]),
72
+ tslib_1.__metadata("design:paramtypes", [String, String, Object, String]),
71
73
  tslib_1.__metadata("design:returntype", Promise)
72
74
  ], EntryProposalResolver.prototype, "applyEntryProposal", null);
73
75
  exports.EntryProposalResolver = EntryProposalResolver = tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"entry-resolver.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport { Like, Not, IsNull } from 'typeorm'\n\nimport { logger } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\nimport { ChatMessage } from '../chat-message/chat-message.js'\nimport { assertMayWriteSession } from '../chat-session/session-write-gate.js'\nimport { isSessionParticipant } from '../chat-session-participant/participant-lookup.js'\nimport { PROPOSAL_OUTCOME_PREFIX } from '../chat-message/proposal-outcome.js'\nimport { toolAccessFor } from '../tool-access.js'\nimport { applyEntryProposal, entryProposalAnswered, type EntryApplyOutcome } from './entry-apply.js'\nimport { runAsCaller } from './entry-tools.js'\n\n@Resolver()\nexport class EntryProposalResolver {\n /*\n * Not `@transaction`: each row is its own call of the application's mutation (entry-apply.ts), so one\n * refused row keeps the others. The dock reports the outcome afterwards through `reportProposalOutcome`.\n */\n @Mutation(() => GraphQLJSON, {\n description:\n 'Enter the rows of a data entry proposal the person pressed. The rows are the stored proposal\\'s; the door, a newer proposal and the target\\'s checks are asked again, and each row is written in its own transaction.'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", domainOwnerGranted: true)')\n async applyEntryProposal(\n @Arg('sessionId') sessionId: string,\n @Arg('proposalKey') proposalKey: string,\n @Ctx() context: ResolverContext\n ): Promise<EntryApplyOutcome> {\n const { domain, user } = context.state\n const session = await getRepository(ChatSession).findOneBy({ id: sessionId, domain: { id: domain.id } } as any)\n if (!session) throw new Error(`ChatSession ${sessionId} not found`)\n await assertMayWriteSession({\n session,\n userId: user?.id,\n isParticipant: () => isSessionParticipant(domain.id, session.id, user?.id),\n t: (key, options) => (context.t ? context.t(key, options) : String(options?.defaultValue ?? key))\n })\n\n const key = (proposalKey ?? '').trim()\n const carrying = await getRepository(ChatMessage).find({\n select: ['id', 'proposals', 'createdAt'] as any,\n where: { session: { id: session.id } as any, proposals: Not(IsNull()) } as any,\n order: { createdAt: 'ASC' } as any\n })\n const lines = await getRepository(ChatMessage).find({\n select: ['content'] as any,\n where: { session: { id: session.id } as any, role: 'system', content: Like(`${PROPOSAL_OUTCOME_PREFIX}${key}:%`) } as any\n })\n\n const passes = toolAccessFor(context)\n return await applyEntryProposal(key, {\n messages: carrying.map((message: any) => ({ id: message.id, proposals: message.proposalsJson ?? [] })),\n answered: entryProposalAnswered(key, lines.map((line: any) => line.content)),\n passesDoor: door => passes(door),\n state: context.state,\n /*\n * The application's API, as the person - the same request state the door would have seen had they typed\n * the row in: domain, user, and what their address and prohibitions say. No `tx` is handed on, so the\n * door opens its own transaction per row (entry-tools.ts `runAsCaller`).\n */\n execute: runAsCaller(context),\n report: (message, error) => logger.error(message, error)\n })\n }\n}\n"]}
1
+ {"version":3,"file":"entry-resolver.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-resolver.ts"],"names":[],"mappings":";;;;AAAA,+CAAsE;AACtE,qDAA6C;AAC7C,qCAA2C;AAE3C,6CAA4C;AAC5C,iDAAqD;AAErD,qEAA6D;AAC7D,qEAA6D;AAC7D,iFAA6E;AAC7E,6FAAwF;AACxF,6EAA6E;AAC7E,sDAAiD;AACjD,qDAAoG;AACpG,qDAA8C;AAGvC,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;OAGG;IAMG,AAAN,KAAK,CAAC,kBAAkB,CACJ,SAAiB,EACf,WAAmB,EAChC,OAAwB,EAC2I,YAAqB;QAE/L,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QACtC,MAAM,OAAO,GAAG,MAAM,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAS,CAAC,CAAA;QAC/G,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,SAAS,YAAY,CAAC,CAAA;QACnE,MAAM,IAAA,6CAAqB,EAAC;YAC1B,OAAO;YACP,MAAM,EAAE,IAAI,EAAE,EAAE;YAChB,aAAa,EAAE,GAAG,EAAE,CAAC,IAAA,4CAAoB,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;YAC1E,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC,CAAC;SAClG,CAAC,CAAA;QAEF,MAAM,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAC,IAAI,CAAC;YACrD,MAAM,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAQ;YAC/C,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS,EAAE,SAAS,EAAE,IAAA,aAAG,EAAC,IAAA,gBAAM,GAAE,CAAC,EAAS;YAC9E,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAS;SACnC,CAAC,CAAA;QACF,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAC,IAAI,CAAC;YAClD,MAAM,EAAE,CAAC,SAAS,CAAQ;YAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAA,cAAI,EAAC,GAAG,6CAAuB,GAAG,GAAG,IAAI,CAAC,EAAS;SAC1H,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,IAAA,8BAAa,EAAC,OAAO,CAAC,CAAA;QACrC,OAAO,MAAM,IAAA,mCAAkB,EAAC,GAAG,EAAE;YACnC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,CAAC;YACtG,QAAQ,EAAE,IAAA,sCAAqB,EAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC5E,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;YAChC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB;;;;eAIG;YACH,OAAO,EAAE,IAAA,4BAAW,EAAC,OAAO,CAAC;YAC7B,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,YAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC;YACxD,YAAY,EAAE,YAAY,IAAI,SAAS;SACxC,CAAC,CAAA;IACJ,CAAC;CACF,CAAA;AArDY,sDAAqB;AAU1B;IALL,IAAA,uBAAQ,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE;QAC3B,WAAW,EACT,uNAAuN;KAC1N,CAAC;IACD,IAAA,wBAAS,EAAC,uFAAuF,CAAC;IAEhG,mBAAA,IAAA,kBAAG,EAAC,WAAW,CAAC,CAAA;IAChB,mBAAA,IAAA,kBAAG,EAAC,aAAa,CAAC,CAAA;IAClB,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kHAAkH,EAAE,CAAC,CAAA;;;;+DAsC1K;gCApDU,qBAAqB;IADjC,IAAA,uBAAQ,GAAE;GACE,qBAAqB,CAqDjC","sourcesContent":["import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport { Like, Not, IsNull } from 'typeorm'\n\nimport { logger } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\nimport { ChatMessage } from '../chat-message/chat-message.js'\nimport { assertMayWriteSession } from '../chat-session/session-write-gate.js'\nimport { isSessionParticipant } from '../chat-session-participant/participant-lookup.js'\nimport { PROPOSAL_OUTCOME_PREFIX } from '../chat-message/proposal-outcome.js'\nimport { toolAccessFor } from '../tool-access.js'\nimport { applyEntryProposal, entryProposalAnswered, type EntryApplyOutcome } from './entry-apply.js'\nimport { runAsCaller } from './entry-tools.js'\n\n@Resolver()\nexport class EntryProposalResolver {\n /*\n * Not `@transaction`: each row is its own call of the application's mutation (entry-apply.ts), so one\n * refused row keeps the others. The dock reports the outcome afterwards through `reportProposalOutcome`.\n */\n @Mutation(() => GraphQLJSON, {\n description:\n 'Enter the rows of a data entry proposal the person pressed. The rows are the stored proposal\\'s; the door, a newer proposal and the target\\'s checks are asked again, and each row is written in its own transaction.'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", domainOwnerGranted: true)')\n async applyEntryProposal(\n @Arg('sessionId') sessionId: string,\n @Arg('proposalKey') proposalKey: string,\n @Ctx() context: ResolverContext,\n @Arg('reviewedHash', { nullable: true, description: 'The fingerprint of the proposal as the AI workspace showed it; a proposal that is no longer that one is refused.' }) reviewedHash?: string\n ): Promise<EntryApplyOutcome> {\n const { domain, user } = context.state\n const session = await getRepository(ChatSession).findOneBy({ id: sessionId, domain: { id: domain.id } } as any)\n if (!session) throw new Error(`ChatSession ${sessionId} not found`)\n await assertMayWriteSession({\n session,\n userId: user?.id,\n isParticipant: () => isSessionParticipant(domain.id, session.id, user?.id),\n t: (key, options) => (context.t ? context.t(key, options) : String(options?.defaultValue ?? key))\n })\n\n const key = (proposalKey ?? '').trim()\n const carrying = await getRepository(ChatMessage).find({\n select: ['id', 'proposals', 'createdAt'] as any,\n where: { session: { id: session.id } as any, proposals: Not(IsNull()) } as any,\n order: { createdAt: 'ASC' } as any\n })\n const lines = await getRepository(ChatMessage).find({\n select: ['content'] as any,\n where: { session: { id: session.id } as any, role: 'system', content: Like(`${PROPOSAL_OUTCOME_PREFIX}${key}:%`) } as any\n })\n\n const passes = toolAccessFor(context)\n return await applyEntryProposal(key, {\n messages: carrying.map((message: any) => ({ id: message.id, proposals: message.proposalsJson ?? [] })),\n answered: entryProposalAnswered(key, lines.map((line: any) => line.content)),\n passesDoor: door => passes(door),\n state: context.state,\n /*\n * The application's API, as the person - the same request state the door would have seen had they typed\n * the row in: domain, user, and what their address and prohibitions say. No `tx` is handed on, so the\n * door opens its own transaction per row (entry-tools.ts `runAsCaller`).\n */\n execute: runAsCaller(context),\n report: (message, error) => logger.error(message, error),\n reviewedHash: reviewedHash || undefined\n })\n }\n}\n"]}