@things-factory/ai-assistant 10.1.114 → 10.1.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/components/assistant-dock.ts +26 -3
- package/client/setup-assistant-dock.ts +3 -2
- package/client/utils/entry-proposal.ts +14 -1
- package/dist-client/components/assistant-dock.d.ts +5 -0
- package/dist-client/components/assistant-dock.js +25 -3
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/setup-assistant-dock.d.ts +1 -0
- package/dist-client/setup-assistant-dock.js +1 -0
- package/dist-client/setup-assistant-dock.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/entry-proposal.d.ts +10 -1
- package/dist-client/utils/entry-proposal.js +13 -1
- package/dist-client/utils/entry-proposal.js.map +1 -1
- package/dist-server/service/data-entry/entry-apply.d.ts +8 -0
- package/dist-server/service/data-entry/entry-apply.js +19 -2
- package/dist-server/service/data-entry/entry-apply.js.map +1 -1
- package/dist-server/service/data-entry/entry-refusal.d.ts +6 -0
- package/dist-server/service/data-entry/entry-refusal.js +25 -0
- package/dist-server/service/data-entry/entry-refusal.js.map +1 -0
- package/dist-server/service/data-entry/entry-resolver.js +2 -0
- package/dist-server/service/data-entry/entry-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/service/data-entry/data-entry-update.test.ts +10 -1
- package/server/service/data-entry/data-entry.test.ts +29 -1
- package/server/service/data-entry/entry-apply.ts +27 -2
- package/server/service/data-entry/entry-refusal.test.ts +73 -0
- package/server/service/data-entry/entry-refusal.ts +20 -0
- package/server/service/data-entry/entry-resolver.ts +2 -0
- package/test/entry-proposal.test.ts +31 -1
|
@@ -40,6 +40,11 @@ export interface EntryRowResult {
|
|
|
40
40
|
}
|
|
41
41
|
export interface EntryApplyAnswer extends ProposalOutcome {
|
|
42
42
|
rows?: EntryRowResult[];
|
|
43
|
+
refusal?: {
|
|
44
|
+
message: string;
|
|
45
|
+
extensions: Record<string, unknown>;
|
|
46
|
+
path: string[];
|
|
47
|
+
};
|
|
43
48
|
/** Set by the handler from the card - which words the conversation line uses. */
|
|
44
49
|
op?: string;
|
|
45
50
|
}
|
|
@@ -52,7 +57,11 @@ export declare function entryCellIssues(row: EntryRowView, field?: string): Entr
|
|
|
52
57
|
/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */
|
|
53
58
|
export declare function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string;
|
|
54
59
|
/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */
|
|
55
|
-
export declare function entryProposalHandler(apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>, t: (key: string, options?: any) => string, reviewedOf?: (key: string) => string | undefined
|
|
60
|
+
export declare function entryProposalHandler(apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>, t: (key: string, options?: any) => string, reviewedOf?: (key: string) => string | undefined, reportRefusal?: (notification: {
|
|
61
|
+
level: 'error';
|
|
62
|
+
message: string;
|
|
63
|
+
ex: NonNullable<EntryApplyAnswer['refusal']>[];
|
|
64
|
+
}) => void | Promise<void>): (proposal: any, context: ProposalContext) => Promise<ProposalOutcome>;
|
|
56
65
|
/**
|
|
57
66
|
* The newest card per record, over the conversation's proposals in order - an older one is replaced and
|
|
58
67
|
* cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).
|
|
@@ -38,7 +38,11 @@ export function entryOutcomeNote(answer, t) {
|
|
|
38
38
|
return [head, ...lines, ...more].join('\n');
|
|
39
39
|
}
|
|
40
40
|
/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */
|
|
41
|
-
export function entryProposalHandler(apply, t, reviewedOf = reviewedHashOf) {
|
|
41
|
+
export function entryProposalHandler(apply, t, reviewedOf = reviewedHashOf, reportRefusal = async (notification) => {
|
|
42
|
+
/* The generic toast transport. auth-ui alone interprets the refusal and offers an access request. */
|
|
43
|
+
const { notify } = await import('@operato/layout');
|
|
44
|
+
notify(notification);
|
|
45
|
+
}) {
|
|
42
46
|
return async (proposal, context) => {
|
|
43
47
|
if (!context.sessionId || !proposal?.key)
|
|
44
48
|
return { verdict: 'unavailable', errorCode: 'no-conversation' };
|
|
@@ -47,6 +51,14 @@ export function entryProposalHandler(apply, t, reviewedOf = reviewedHashOf) {
|
|
|
47
51
|
if (!reviewStillHolds(proposal, reviewed))
|
|
48
52
|
return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) };
|
|
49
53
|
const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op };
|
|
54
|
+
if (answer.verdict === 'refused' && answer.errorCode === 'door-closed' && answer.refusal) {
|
|
55
|
+
try {
|
|
56
|
+
await reportRefusal({ level: 'error', message: answer.refusal.message, ex: [answer.refusal] });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* Notification delivery is best-effort; it never changes the stored proposal's refusal. */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
50
62
|
if (answer.errorCode === 'review-stale')
|
|
51
63
|
return { verdict: 'refused', errorCode: 'review-stale', applied: 0, missed: 0, note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) };
|
|
52
64
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-proposal.js","sourceRoot":"","sources":["../../client/utils/entry-proposal.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AA4CvE,MAAM,UAAU,eAAe,CAAC,QAAa;IAC3C,OAAO,QAAQ,EAAE,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAa;IAC3C,MAAM,IAAI,GAAmB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAA;IACjD,MAAM,OAAO,GAAG,CAAC,GAAiB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAChF,IAAI,CAAC,QAAQ,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAA;IACvE,uHAAuH;IACvH,MAAM,KAAK,GAAG,CAAC,GAAiB,EAAE,EAAE;QAClC,MAAM,KAAK,GAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QACjD,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IACxJ,CAAC,CAAA;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;IACpD,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AACxD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,KAAqB;IACjD,OAAO,KAAK,CAAC,UAAU,IAAI,4BAA4B,KAAK,CAAC,IAAI,EAAE,CAAA;AACrE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,eAAe,CAAC,GAAiB,EAAE,KAAc;IAC/D,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,gBAAgB,CAAC,MAAwB,EAAE,CAAyC;IAClG,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,KAAK,QAAQ;QACjC,CAAC,CAAC,MAAM,CAAC,OAAO;YACd,CAAC,CAAC,CAAC,CAAC,4BAA4B,EAAE,EAAE,YAAY,EAAE,0BAA0B,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACtG,CAAC,CAAC,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,wBAAwB,EAAE,CAAC;QACpF,CAAC,CAAC,MAAM,CAAC,OAAO;YACd,CAAC,CAAC,CAAC,CAAC,4BAA4B,EAAE,EAAE,YAAY,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACnG,CAAC,CAAC,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,CAAC,CAAA;IACnF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,gCAAgC,EAAE,EAAE,YAAY,EAAE,mBAAmB,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;IACpK,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,qCAAqC,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACnK,OAAO,CAAC,IAAI,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,oBAAoB,CAClC,KAAmG,EACnG,CAAyC,EACzC,aAAkD,cAAc;IAEhE,OAAO,KAAK,EAAE,QAAa,EAAE,OAAwB,EAA4B,EAAE;QACjF,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,GAAG;YAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAA;QACzG,4HAA4H;QAC5H,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QACzC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,iGAAiG,EAAE,CAAC,EAAS,CAAA;QAC3Q,MAAM,MAAM,GAAG,EAAE,GAAG,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAA;QAC/F,IAAI,MAAM,CAAC,SAAS,KAAK,cAAc;YACrC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,iGAAiG,EAAE,CAAC,EAAE,CAAA;QAClP,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,CAAC;YAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC;YAC1B,IAAI,EAAE,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC;SAClC,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,gBAAyB;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,IAAI,IAAI,gBAAgB;QAAE,KAAK,MAAM,GAAG,IAAI,IAAI,IAAI,EAAE;YAAE,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9I,OAAO,MAAM,CAAA;AACf,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,YAAY,CAAC,QAAa;IACxC,OAAO,GAAG,QAAQ,EAAE,MAAM,IAAI,QAAQ,EAAE,EAAE,IAAI,QAAQ,EAAE,CAAA;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,QAAa,EAAE,CAAyC;IACjF,MAAM,IAAI,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAA;IAC/E,OAAO,QAAQ,EAAE,EAAE,KAAK,QAAQ;QAC9B,CAAC,CAAC,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;QAClF,CAAC,CAAC,CAAC,CAAC,iCAAiC,EAAE,EAAE,YAAY,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAA;AACrF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAa;IAC5C,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAA;AAC9D,CAAC","sourcesContent":["/*\n * The data entry card's rules and its handler (operato-application/design/ai/data-entry.md §5).\n *\n * The card is a review, not an editor: a correction is said in the conversation and the model proposes\n * again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the\n * screen sends no rows of its own.\n */\nimport type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'\nimport { reviewedHashOf, reviewStillHolds } from './entry-workspace.js'\n\nexport interface EntryIssueView {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n paramKeys?: Record<string, string>\n blocking: boolean\n}\n\nexport interface EntryRowView {\n index: number\n values: Record<string, unknown>\n sources: { attachmentId?: string; where?: string; person?: boolean }[]\n issues: EntryIssueView[]\n /** The server found this row's numbers and dates together on this line of the cited file. */\n evidence?: { attachmentId: string; line: number }\n}\n\nexport interface EntryTotalView {\n field: string\n all: number\n ready: number\n declared?: number\n declaredFound?: boolean\n difference?: number\n declaredBy?: 'model' | 'file'\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n error?: string\n}\n\nexport interface EntryApplyAnswer extends ProposalOutcome {\n rows?: EntryRowResult[]\n /** Set by the handler from the card - which words the conversation line uses. */\n op?: string\n}\n\nexport function isEntryProposal(proposal: any): boolean {\n return proposal?.kind === 'entry' && Array.isArray(proposal?.rows)\n}\n\nexport function entryReadyCount(proposal: any): number {\n const rows: EntryRowView[] = proposal?.rows ?? []\n const blocked = (row: EntryRowView) => row.issues?.some(issue => issue.blocking)\n if (!proposal?.groupBy) return rows.filter(row => !blocked(row)).length\n /* Lines of one record go in together - a record with a line kept out is kept out whole, and its lines do not count. */\n const keyOf = (row: EntryRowView) => {\n const value: any = row.values?.[proposal.groupBy]\n return value === undefined || value === null || value === '' ? `row-${row.index}` : String(typeof value === 'object' ? value.id ?? value.text : value)\n }\n const out = new Set(rows.filter(blocked).map(keyOf))\n return rows.filter(row => !out.has(keyOf(row))).length\n}\n\n/** The i18n key of an issue - the target's own, or the framework's by code. */\nexport function entryIssueKey(issue: EntryIssueView): string {\n return issue.messageKey || `ai-assistant.entry.issue.${issue.code}`\n}\n\n/** The issues of one cell, and of the row as a whole (no field). */\nexport function entryCellIssues(row: EntryRowView, field?: string): EntryIssueView[] {\n return (row.issues ?? []).filter(issue => (field ? issue.field === field : !issue.field))\n}\n\n/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */\nexport function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string {\n const refused = (answer.rows ?? []).filter(row => !row.ok)\n const head = answer.op === 'update'\n ? answer.applied\n ? t('ai-assistant.entry.changed', { defaultValue: 'changed {count} records.', count: answer.applied })\n : t('ai-assistant.entry.changed-none', { defaultValue: 'no record was changed.' })\n : answer.applied\n ? t('ai-assistant.entry.entered', { defaultValue: 'entered {count} rows.', count: answer.applied })\n : t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })\n const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))\n const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []\n return [head, ...lines, ...more].join('\\n')\n}\n\n/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */\nexport function entryProposalHandler(\n apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>,\n t: (key: string, options?: any) => string,\n reviewedOf: (key: string) => string | undefined = reviewedHashOf\n) {\n return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n /* Reviewed in the 작업판: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */\n const reviewed = reviewedOf(proposal.key)\n if (!reviewStillHolds(proposal, reviewed)) return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) } as any\n const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op }\n if (answer.errorCode === 'review-stale')\n return { verdict: 'refused', errorCode: 'review-stale', applied: 0, missed: 0, note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) }\n return {\n verdict: answer.verdict,\n ...(answer.errorCode ? { errorCode: answer.errorCode } : {}),\n applied: answer.applied ?? 0,\n missed: answer.missed ?? 0,\n note: entryOutcomeNote(answer, t)\n }\n }\n}\n\n/**\n * The newest card per record, over the conversation's proposals in order - an older one is replaced and\n * cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).\n */\nexport function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string> {\n const latest = new Map<string, string>()\n for (const list of proposalsInOrder) for (const one of list ?? []) if (isEntryProposal(one) && one.key) latest.set(entryGroupOf(one), one.key)\n return latest\n}\n\n/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */\nexport function entryGroupOf(proposal: any): string {\n return `${proposal?.target}:${proposal?.op ?? 'create'}`\n}\n\n/**\n * The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a\n * 수정안 is never titled as a 입력안.\n */\nexport function entryTitle(proposal: any, t: (key: string, options?: any) => string): string {\n const name = proposal?.labelKey ? t(proposal.labelKey) : proposal?.target ?? ''\n return proposal?.op === 'update'\n ? t('ai-assistant.entry.title-update', { defaultValue: '{name} to change', name })\n : t('ai-assistant.entry.title-create', { defaultValue: '{name} to enter', name })\n}\n\nexport function isChangeProposal(proposal: any): boolean {\n return isEntryProposal(proposal) && proposal.op === 'update'\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entry-proposal.js","sourceRoot":"","sources":["../../client/utils/entry-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The data entry card's rules and its handler (operato-application/design/ai/data-entry.md §5).\n *\n * The card is a review, not an editor: a correction is said in the conversation and the model proposes\n * again - the newer card replaces the older one. Pressing asks the server to enter the stored rows; the\n * screen sends no rows of its own.\n */\nimport type { ProposalContext, ProposalOutcome } from './assistant-proposals.js'\nimport { reviewedHashOf, reviewStillHolds } from './entry-workspace.js'\n\nexport interface EntryIssueView {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n paramKeys?: Record<string, string>\n blocking: boolean\n}\n\nexport interface EntryRowView {\n index: number\n values: Record<string, unknown>\n sources: { attachmentId?: string; where?: string; person?: boolean }[]\n issues: EntryIssueView[]\n /** The server found this row's numbers and dates together on this line of the cited file. */\n evidence?: { attachmentId: string; line: number }\n}\n\nexport interface EntryTotalView {\n field: string\n all: number\n ready: number\n declared?: number\n declaredFound?: boolean\n difference?: number\n declaredBy?: 'model' | 'file'\n}\n\nexport interface EntryRowResult {\n index: number\n ok: boolean\n id?: string\n label?: string\n error?: string\n}\n\nexport interface EntryApplyAnswer extends ProposalOutcome {\n rows?: EntryRowResult[]\n refusal?: { message: string; extensions: Record<string, unknown>; path: string[] }\n /** Set by the handler from the card - which words the conversation line uses. */\n op?: string\n}\n\nexport function isEntryProposal(proposal: any): boolean {\n return proposal?.kind === 'entry' && Array.isArray(proposal?.rows)\n}\n\nexport function entryReadyCount(proposal: any): number {\n const rows: EntryRowView[] = proposal?.rows ?? []\n const blocked = (row: EntryRowView) => row.issues?.some(issue => issue.blocking)\n if (!proposal?.groupBy) return rows.filter(row => !blocked(row)).length\n /* Lines of one record go in together - a record with a line kept out is kept out whole, and its lines do not count. */\n const keyOf = (row: EntryRowView) => {\n const value: any = row.values?.[proposal.groupBy]\n return value === undefined || value === null || value === '' ? `row-${row.index}` : String(typeof value === 'object' ? value.id ?? value.text : value)\n }\n const out = new Set(rows.filter(blocked).map(keyOf))\n return rows.filter(row => !out.has(keyOf(row))).length\n}\n\n/** The i18n key of an issue - the target's own, or the framework's by code. */\nexport function entryIssueKey(issue: EntryIssueView): string {\n return issue.messageKey || `ai-assistant.entry.issue.${issue.code}`\n}\n\n/** The issues of one cell, and of the row as a whole (no field). */\nexport function entryCellIssues(row: EntryRowView, field?: string): EntryIssueView[] {\n return (row.issues ?? []).filter(issue => (field ? issue.field === field : !issue.field))\n}\n\n/** What stays in the conversation - the model reads it on its next turn, and the person sees what went in. */\nexport function entryOutcomeNote(answer: EntryApplyAnswer, t: (key: string, options?: any) => string): string {\n const refused = (answer.rows ?? []).filter(row => !row.ok)\n const head = answer.op === 'update'\n ? answer.applied\n ? t('ai-assistant.entry.changed', { defaultValue: 'changed {count} records.', count: answer.applied })\n : t('ai-assistant.entry.changed-none', { defaultValue: 'no record was changed.' })\n : answer.applied\n ? t('ai-assistant.entry.entered', { defaultValue: 'entered {count} rows.', count: answer.applied })\n : t('ai-assistant.entry.entered-none', { defaultValue: 'no row was entered.' })\n const lines = refused.slice(0, 20).map(row => t('ai-assistant.entry.row-refused', { defaultValue: 'row {n}: {reason}', n: row.index + 1, reason: row.error || '' }))\n const more = refused.length > 20 ? [t('ai-assistant.entry.more-refused', { defaultValue: '{count} more rows were not entered.', count: refused.length - 20 })] : []\n return [head, ...lines, ...more].join('\\n')\n}\n\n/** The handler every entry card runs - built with the mutation so the rules are tested without a server. */\nexport function entryProposalHandler(\n apply: (sessionId: string, proposalKey: string, reviewedHash?: string) => Promise<EntryApplyAnswer>,\n t: (key: string, options?: any) => string,\n reviewedOf: (key: string) => string | undefined = reviewedHashOf,\n reportRefusal: (notification: { level: 'error'; message: string; ex: NonNullable<EntryApplyAnswer['refusal']>[] }) => void | Promise<void> = async notification => {\n /* The generic toast transport. auth-ui alone interprets the refusal and offers an access request. */\n const { notify } = await import('@operato/layout')\n notify(notification)\n }\n) {\n return async (proposal: any, context: ProposalContext): Promise<ProposalOutcome> => {\n if (!context.sessionId || !proposal?.key) return { verdict: 'unavailable', errorCode: 'no-conversation' }\n /* Reviewed in the 작업판: entered only as it was shown there (design/ai/workspace.md §6-1) - here, and again at the server. */\n const reviewed = reviewedOf(proposal.key)\n if (!reviewStillHolds(proposal, reviewed)) return { verdict: 'refused', errorCode: 'review-stale', note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) } as any\n const answer = { ...(await apply(context.sessionId, proposal.key, reviewed)), op: proposal.op }\n if (answer.verdict === 'refused' && answer.errorCode === 'door-closed' && answer.refusal) {\n try {\n await reportRefusal({ level: 'error', message: answer.refusal.message, ex: [answer.refusal] })\n } catch {\n /* Notification delivery is best-effort; it never changes the stored proposal's refusal. */\n }\n }\n if (answer.errorCode === 'review-stale')\n return { verdict: 'refused', errorCode: 'review-stale', applied: 0, missed: 0, note: t('ai-assistant.entry.review-stale', { defaultValue: 'this proposal changed after it was reviewed in the workspace - review it again before entering.' }) }\n return {\n verdict: answer.verdict,\n ...(answer.errorCode ? { errorCode: answer.errorCode } : {}),\n applied: answer.applied ?? 0,\n missed: answer.missed ?? 0,\n note: entryOutcomeNote(answer, t)\n }\n }\n}\n\n/**\n * The newest card per record, over the conversation's proposals in order - an older one is replaced and\n * cannot be entered (the server refuses it as `superseded`; the card says so before anyone presses).\n */\nexport function latestEntryKeys(proposalsInOrder: any[][]): Map<string, string> {\n const latest = new Map<string, string>()\n for (const list of proposalsInOrder) for (const one of list ?? []) if (isEntryProposal(one) && one.key) latest.set(entryGroupOf(one), one.key)\n return latest\n}\n\n/** A 입력안 and a 수정안 of one record are counted apart - a newer one of the same kind replaces the older. */\nexport function entryGroupOf(proposal: any): string {\n return `${proposal?.target}:${proposal?.op ?? 'create'}`\n}\n\n/**\n * The card's title - the record's name from the target (`부서`, `거래처`) and the kind of card from here, so a\n * 수정안 is never titled as a 입력안.\n */\nexport function entryTitle(proposal: any, t: (key: string, options?: any) => string): string {\n const name = proposal?.labelKey ? t(proposal.labelKey) : proposal?.target ?? ''\n return proposal?.op === 'update'\n ? t('ai-assistant.entry.title-update', { defaultValue: '{name} to change', name })\n : t('ai-assistant.entry.title-create', { defaultValue: '{name} to enter', name })\n}\n\nexport function isChangeProposal(proposal: any): boolean {\n return isEntryProposal(proposal) && proposal.op === 'update'\n}\n"]}
|
|
@@ -11,12 +11,19 @@ export interface EntryRowResult {
|
|
|
11
11
|
/** A blocking issue kept it out, or the target refused it. */
|
|
12
12
|
error?: string;
|
|
13
13
|
}
|
|
14
|
+
export interface EntryRefusal {
|
|
15
|
+
message: string;
|
|
16
|
+
extensions: Record<string, unknown>;
|
|
17
|
+
path: string[];
|
|
18
|
+
}
|
|
14
19
|
export interface EntryApplyOutcome {
|
|
15
20
|
verdict: 'accepted' | 'refused' | 'unavailable';
|
|
16
21
|
errorCode?: string;
|
|
17
22
|
applied: number;
|
|
18
23
|
missed: number;
|
|
19
24
|
rows: EntryRowResult[];
|
|
25
|
+
/** AUTH's serialized refusal, only when the closed door could be identified. */
|
|
26
|
+
refusal?: EntryRefusal;
|
|
20
27
|
}
|
|
21
28
|
export interface EntryApplyDeps {
|
|
22
29
|
/** The conversation's messages that carry proposals, oldest first. */
|
|
@@ -24,6 +31,7 @@ export interface EntryApplyDeps {
|
|
|
24
31
|
/** Whether an outcome line already answers this key. */
|
|
25
32
|
answered: boolean;
|
|
26
33
|
passesDoor(door: string): Promise<boolean>;
|
|
34
|
+
refusalFor?(door: string): Promise<EntryRefusal | undefined>;
|
|
27
35
|
/**
|
|
28
36
|
* Runs one of the application's queries or mutations with the person's request state - its API, its directives
|
|
29
37
|
* and its own checks. Answers what GraphQL answers.
|
|
@@ -20,6 +20,23 @@ const entry_update_js_1 = require("./entry-update.js");
|
|
|
20
20
|
const entry_target_js_1 = require("./entry-target.js");
|
|
21
21
|
const entry_hash_js_1 = require("./entry-hash.js");
|
|
22
22
|
const nothing = (verdict, errorCode) => ({ verdict, errorCode, applied: 0, missed: 0, rows: [] });
|
|
23
|
+
/** Called after the existing audit. An unavailable explanation must not change the refusal. */
|
|
24
|
+
async function closedDoor(door, deps) {
|
|
25
|
+
const outcome = nothing('refused', 'door-closed');
|
|
26
|
+
try {
|
|
27
|
+
const refusal = await deps.refusalFor?.(door);
|
|
28
|
+
return refusal ? { ...outcome, refusal } : outcome;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
try {
|
|
32
|
+
deps.report?.('[ai-assistant] entry refusal explanation unavailable', error);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* A diagnostic sink cannot turn a closed door into a failed proposal request. */
|
|
36
|
+
}
|
|
37
|
+
return outcome;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
23
40
|
function findEntryProposal(messages, key) {
|
|
24
41
|
let at = -1;
|
|
25
42
|
let proposal;
|
|
@@ -57,7 +74,7 @@ async function applyEntryProposal(key, deps, targetOf = entry_target_js_1.entryT
|
|
|
57
74
|
const door = (0, entry_target_js_1.entryDoorOf)(target.mutation);
|
|
58
75
|
if (!(await deps.passesDoor(door))) {
|
|
59
76
|
await (0, entry_target_js_1.recordEntryEvent)(target, { phase: 'apply', tool: (0, entry_target_js_1.entryToolName)(target.key), rows: 0, ok: false, error: 'door-closed', detail: { proposalKey: key } }, { state: deps.state });
|
|
60
|
-
return
|
|
77
|
+
return closedDoor(door, deps);
|
|
61
78
|
}
|
|
62
79
|
/* Read again from the stored values - the same rules, and today's records for the target's checks. */
|
|
63
80
|
let { rows } = (0, entry_target_js_1.normalizeEntryRows)(target, (proposal.rows ?? []).map((row) => ({ values: row.values, sources: row.sources })));
|
|
@@ -146,7 +163,7 @@ async function applyChanges(key, proposal, target, deps) {
|
|
|
146
163
|
for (const door of [(0, entry_target_js_1.rootFieldOf)(update.mutation, 'mutation'), (0, entry_target_js_1.rootFieldOf)(update.read.query, 'query')]) {
|
|
147
164
|
if (!(await deps.passesDoor(door))) {
|
|
148
165
|
await (0, entry_target_js_1.recordEntryEvent)(target, { phase: 'apply', tool, rows: 0, ok: false, error: 'door-closed', detail: { proposalKey: key } }, { state: deps.state });
|
|
149
|
-
return
|
|
166
|
+
return closedDoor(door, deps);
|
|
150
167
|
}
|
|
151
168
|
}
|
|
152
169
|
const stored = proposal.rows ?? [];
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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 EntryRefusal {\n message: string\n extensions: Record<string, unknown>\n path: string[]\n}\n\nexport interface EntryApplyOutcome {\n verdict: 'accepted' | 'refused' | 'unavailable'\n errorCode?: string\n applied: number\n missed: number\n rows: EntryRowResult[]\n /** AUTH's serialized refusal, only when the closed door could be identified. */\n refusal?: EntryRefusal\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 refusalFor?(door: string): Promise<EntryRefusal | undefined>\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\n/** Called after the existing audit. An unavailable explanation must not change the refusal. */\nasync function closedDoor(door: string, deps: EntryApplyDeps): Promise<EntryApplyOutcome> {\n const outcome = nothing('refused', 'door-closed')\n try {\n const refusal = await deps.refusalFor?.(door)\n return refusal ? { ...outcome, refusal } : outcome\n } catch (error) {\n try {\n deps.report?.('[ai-assistant] entry refusal explanation unavailable', error)\n } catch {\n /* A diagnostic sink cannot turn a closed door into a failed proposal request. */\n }\n return outcome\n }\n}\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 closedDoor(door, deps)\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 closedDoor(door, deps)\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,6 @@
|
|
|
1
|
+
import type { EntryRefusal } from './entry-apply.js';
|
|
2
|
+
/** Presentation only: AUTH still guards the application's mutation when it is executed. */
|
|
3
|
+
export declare function entryRefusalFor(context: {
|
|
4
|
+
state?: any;
|
|
5
|
+
t?: any;
|
|
6
|
+
} | undefined): (door: string) => Promise<EntryRefusal | undefined>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.entryRefusalFor = entryRefusalFor;
|
|
4
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
5
|
+
const door_access_js_1 = require("../chat-message/door-access.js");
|
|
6
|
+
/** Presentation only: AUTH still guards the application's mutation when it is executed. */
|
|
7
|
+
function entryRefusalFor(context) {
|
|
8
|
+
return async (door) => {
|
|
9
|
+
const { domain, user, unsafeIP, prohibitedPrivileges } = context?.state ?? {};
|
|
10
|
+
const field = (0, door_access_js_1.doorField)(door);
|
|
11
|
+
if (!domain || !user || !field)
|
|
12
|
+
return undefined;
|
|
13
|
+
const asked = (0, auth_base_1.privilegeOfField)(field.typeName, field.fieldName);
|
|
14
|
+
if (!asked.known || !asked.gate)
|
|
15
|
+
return undefined;
|
|
16
|
+
const judgement = await (0, auth_base_1.judgePermission)(asked.gate, user, domain, unsafeIP, prohibitedPrivileges);
|
|
17
|
+
/* The gate may have opened since the preflight: never invent a refusal to explain that race. */
|
|
18
|
+
if (judgement.permitted !== false)
|
|
19
|
+
return undefined;
|
|
20
|
+
const operation = field.typeName === 'Mutation' ? 'mutation' : field.typeName === 'Query' ? 'query' : 'subscription';
|
|
21
|
+
const error = (0, auth_base_1.privilegeRejection)(context, asked.gate.category, asked.gate.privilege, judgement.because, operation);
|
|
22
|
+
return { message: error.message, extensions: error.extensions, path: [field.fieldName] };
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=entry-refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-refusal.js","sourceRoot":"","sources":["../../../server/service/data-entry/entry-refusal.ts"],"names":[],"mappings":";;AAKA,0CAcC;AAnBD,yDAAiG;AACjG,mEAA0D;AAG1D,2FAA2F;AAC3F,SAAgB,eAAe,CAAC,OAA6C;IAC3E,OAAO,KAAK,EAAC,IAAI,EAAC,EAAE;QAClB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,EAAE,KAAK,IAAI,EAAE,CAAA;QAC7E,MAAM,KAAK,GAAG,IAAA,0BAAS,EAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAA;QAChD,MAAM,KAAK,GAAG,IAAA,4BAAgB,EAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/D,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,OAAO,SAAS,CAAA;QACjD,MAAM,SAAS,GAAG,MAAM,IAAA,2BAAe,EAAC,KAAK,CAAC,IAAW,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAA;QACxG,gGAAgG;QAChG,IAAI,SAAS,CAAC,SAAS,KAAK,KAAK;YAAE,OAAO,SAAS,CAAA;QACnD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAA;QACpH,MAAM,KAAK,GAAG,IAAA,8BAAkB,EAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAClH,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAA;IAC1F,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { judgePermission, privilegeOfField, privilegeRejection } from '@things-factory/auth-base'\nimport { doorField } from '../chat-message/door-access.js'\nimport type { EntryRefusal } from './entry-apply.js'\n\n/** Presentation only: AUTH still guards the application's mutation when it is executed. */\nexport function entryRefusalFor(context: { state?: any; t?: any } | undefined): (door: string) => Promise<EntryRefusal | undefined> {\n return async door => {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context?.state ?? {}\n const field = doorField(door)\n if (!domain || !user || !field) return undefined\n const asked = privilegeOfField(field.typeName, field.fieldName)\n if (!asked.known || !asked.gate) return undefined\n const judgement = await judgePermission(asked.gate as any, user, domain, unsafeIP, prohibitedPrivileges)\n /* The gate may have opened since the preflight: never invent a refusal to explain that race. */\n if (judgement.permitted !== false) return undefined\n const operation = field.typeName === 'Mutation' ? 'mutation' : field.typeName === 'Query' ? 'query' : 'subscription'\n const error = privilegeRejection(context, asked.gate.category, asked.gate.privilege, judgement.because, operation)\n return { message: error.message, extensions: error.extensions, path: [field.fieldName] }\n }\n}\n"]}
|
|
@@ -15,6 +15,7 @@ const proposal_outcome_js_1 = require("../chat-message/proposal-outcome.js");
|
|
|
15
15
|
const tool_access_js_1 = require("../tool-access.js");
|
|
16
16
|
const entry_apply_js_1 = require("./entry-apply.js");
|
|
17
17
|
const entry_tools_js_1 = require("./entry-tools.js");
|
|
18
|
+
const entry_refusal_js_1 = require("./entry-refusal.js");
|
|
18
19
|
let EntryProposalResolver = class EntryProposalResolver {
|
|
19
20
|
/*
|
|
20
21
|
* Not `@transaction`: each row is its own call of the application's mutation (entry-apply.ts), so one
|
|
@@ -46,6 +47,7 @@ let EntryProposalResolver = class EntryProposalResolver {
|
|
|
46
47
|
messages: carrying.map((message) => ({ id: message.id, proposals: message.proposalsJson ?? [] })),
|
|
47
48
|
answered: (0, entry_apply_js_1.entryProposalAnswered)(key, lines.map((line) => line.content)),
|
|
48
49
|
passesDoor: door => passes(door),
|
|
50
|
+
refusalFor: (0, entry_refusal_js_1.entryRefusalFor)(context),
|
|
49
51
|
state: context.state,
|
|
50
52
|
/*
|
|
51
53
|
* The application's API, as the person - the same request state the door would have seen had they typed
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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'\nimport { entryRefusalFor } from './entry-refusal.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 refusalFor: entryRefusalFor(context),\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"]}
|