@things-factory/station 10.1.42 → 10.1.45

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.
@@ -8,6 +8,6 @@ import { StationServiceResolver } from './station-service-resolver.js';
8
8
  import { StationAssignmentResolver } from './station-assignment-resolver.js';
9
9
  import { StationScreenControlWithdrawalResolver } from './station-screen-control-withdrawal-resolver.js';
10
10
  import { StationScreenPublishSubscriber } from './station-screen-publish-subscriber.js';
11
- export declare const entities: (typeof StationScreenRelease | typeof StationScreenSpec | typeof StationScreenAssignment)[];
12
- export declare const resolvers: (typeof StationScreenControlWithdrawalResolver | typeof StationScreenSpecResolver | typeof StationAssignmentResolver | typeof StationScreenListResolver | typeof StationScreenDraftResolver | typeof StationServiceResolver)[];
11
+ export declare const entities: (typeof StationScreenAssignment | typeof StationScreenSpec | typeof StationScreenRelease)[];
12
+ export declare const resolvers: (typeof StationAssignmentResolver | typeof StationScreenSpecResolver | typeof StationScreenListResolver | typeof StationScreenDraftResolver | typeof StationServiceResolver | typeof StationScreenControlWithdrawalResolver)[];
13
13
  export declare const subscribers: (typeof StationScreenPublishSubscriber)[];
@@ -6,4 +6,4 @@
6
6
  * the set is closed at all and why it lives there; this one exists so the server keeps importing
7
7
  * the axes by the name it always did, and so `checkFlow` has a local place to reach for them.
8
8
  */
9
- export { STATION_AXES, STATION_CORE_AXES, STATION_TRANSITIONAL_AXES, isStationAxis, nearestAxes } from '../../../shared/station-axes.js';
9
+ export { STATION_AXES, STATION_CORE_AXES, isStationAxis, nearestAxes, wrongDeclaredAxes } from '../../../shared/station-axes.js';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.nearestAxes = exports.isStationAxis = exports.STATION_TRANSITIONAL_AXES = exports.STATION_CORE_AXES = exports.STATION_AXES = void 0;
3
+ exports.wrongDeclaredAxes = exports.nearestAxes = exports.isStationAxis = exports.STATION_CORE_AXES = exports.STATION_AXES = void 0;
4
4
  /**
5
5
  * The closed set of context axes, as the server sees it.
6
6
  *
@@ -12,7 +12,7 @@ exports.nearestAxes = exports.isStationAxis = exports.STATION_TRANSITIONAL_AXES
12
12
  var station_axes_js_1 = require("../../../shared/station-axes.js");
13
13
  Object.defineProperty(exports, "STATION_AXES", { enumerable: true, get: function () { return station_axes_js_1.STATION_AXES; } });
14
14
  Object.defineProperty(exports, "STATION_CORE_AXES", { enumerable: true, get: function () { return station_axes_js_1.STATION_CORE_AXES; } });
15
- Object.defineProperty(exports, "STATION_TRANSITIONAL_AXES", { enumerable: true, get: function () { return station_axes_js_1.STATION_TRANSITIONAL_AXES; } });
16
15
  Object.defineProperty(exports, "isStationAxis", { enumerable: true, get: function () { return station_axes_js_1.isStationAxis; } });
17
16
  Object.defineProperty(exports, "nearestAxes", { enumerable: true, get: function () { return station_axes_js_1.nearestAxes; } });
17
+ Object.defineProperty(exports, "wrongDeclaredAxes", { enumerable: true, get: function () { return station_axes_js_1.wrongDeclaredAxes; } });
18
18
  //# sourceMappingURL=station-axes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"station-axes.js","sourceRoot":"","sources":["../../../server/service/station/station-axes.ts"],"names":[],"mappings":";;;AAAA;;;;;;;GAOG;AACH,mEAAwI;AAA/H,+GAAA,YAAY,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAAE,4HAAA,yBAAyB,OAAA;AAAE,gHAAA,aAAa,OAAA;AAAE,8GAAA,WAAW,OAAA","sourcesContent":["/**\n * The closed set of context axes, as the server sees it.\n *\n * The names themselves are declared once, in `shared/station-axes.js`, so that the browser's\n * authoring surface and this gate cannot hold two lists that drift apart. Read that file for why\n * the set is closed at all and why it lives there; this one exists so the server keeps importing\n * the axes by the name it always did, and so `checkFlow` has a local place to reach for them.\n */\nexport { STATION_AXES, STATION_CORE_AXES, STATION_TRANSITIONAL_AXES, isStationAxis, nearestAxes } from '../../../shared/station-axes.js'\n"]}
1
+ {"version":3,"file":"station-axes.js","sourceRoot":"","sources":["../../../server/service/station/station-axes.ts"],"names":[],"mappings":";;;AAAA;;;;;;;GAOG;AACH,mEAAgI;AAAvH,+GAAA,YAAY,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAAE,gHAAA,aAAa,OAAA;AAAE,8GAAA,WAAW,OAAA;AAAE,oHAAA,iBAAiB,OAAA","sourcesContent":["/**\n * The closed set of context axes, as the server sees it.\n *\n * The names themselves are declared once, in `shared/station-axes.js`, so that the browser's\n * authoring surface and this gate cannot hold two lists that drift apart. Read that file for why\n * the set is closed at all and why it lives there; this one exists so the server keeps importing\n * the axes by the name it always did, and so `checkFlow` has a local place to reach for them.\n */\nexport { STATION_AXES, STATION_CORE_AXES, isStationAxis, nearestAxes, wrongDeclaredAxes } from '../../../shared/station-axes.js'\n"]}
@@ -48,6 +48,13 @@ export interface StationFacts {
48
48
  name: string;
49
49
  fields: StationServiceField[];
50
50
  }[];
51
+ /**
52
+ * The axes this product declared beyond the machine's core (`StationProduct.axes`).
53
+ *
54
+ * The generator draws a product's axis only when the product declared it. Drawn anyway, the flow it built
55
+ * would be refused by the authoring check the moment it is saved — the generator's own output failing its gate.
56
+ */
57
+ axes?: readonly string[];
51
58
  }
52
59
  /** Something the generator did not put in, and why. The screen says it in the reader's language. */
53
60
  export interface StationOmission {
@@ -21,7 +21,10 @@ function generateFlow(facts) {
21
21
  reads.push('location.name');
22
22
  }
23
23
  if (facts.hasJobOrders) {
24
- reads.push('order.name', 'materialDefinition.name');
24
+ reads.push('order.name');
25
+ /* What is being made, when the product has a word for it. */
26
+ if (facts.axes?.includes('materialDefinition.name'))
27
+ reads.push('materialDefinition.name');
25
28
  }
26
29
  else {
27
30
  omitted.push({
@@ -1 +1 @@
1
- {"version":3,"file":"station-generate.js","sourceRoot":"","sources":["../../../server/service/station/station-generate.ts"],"names":[],"mappings":"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","sourcesContent":["import { suggestSource } from './station-argument-sources.js'\nimport type { StationArgumentSource, StationFlow, StationServiceField, StationStep } from './station-flow.js'\n\n/**\n * A station screen from what the factory already declares — **rules, not inference.**\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why a rule generator comes before an AI one\n *\n * Filling in a service's required fields is **arithmetic, not judgement**. The equipment is the\n * station's equipment; the operation is the operation; `status` is what the button means. There is\n * exactly one right answer and it is derivable, so a model that guesses it can only be right by\n * accident and wrong without saying so.\n *\n * What is left for a sentence in plain language — «검사도 같이 하는 자리다» — is which *services*\n * belong here. That is the part worth asking a person, or eventually a model, and it is a small\n * closed choice rather than an open one.\n *\n * ## What it leaves out is part of the output\n *\n * A generator that quietly omits the inspection step leaves the author thinking they were given\n * everything. They add it by hand, and it goes to the floor as a box that never fills, because the\n * equipment has no test specification behind it. So every omission is returned with its reason.\n *\n * ## It closes the loop it opens\n *\n * `goodCount` has no automatic source — a person types it. The generator is the one creating the\n * number pad, so it is also the one that can point the argument at it. A generator that produced\n * the pad and left the wiring undone would hand the author the exact defect this module was built\n * to warn about.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** What the factory says about this station. Gathered by the resolver; this module reads only. */\nexport interface StationFacts {\n equipmentName: string\n /** Where it stands. Drawn so an operator can tell one bench from the next. */\n locationName?: string\n /** The operation this bench runs, when the assignment names one. */\n operationKey?: string\n /** ISO 8601, e.g. `PT392S`. Decides how often the screen re-reads. */\n operationDuration?: string\n /** Test specifications the equipment carries. Empty means no inspection step. */\n testSpecifications: string[]\n /** Stop reasons this factory declares. Empty means a pause button with no reason to give. */\n stopReasons: string[]\n /** Whether any job order names this operation. Decides whether order fields are worth drawing. */\n hasJobOrders: boolean\n /** Every service a step could call, with its fields — the schema's own list. */\n services: { name: string; fields: StationServiceField[] }[]\n}\n\n/** Something the generator did not put in, and why. The screen says it in the reader's language. */\nexport interface StationOmission {\n code: string\n params?: Record<string, string>\n /** The same thing in Korean, for logs and the scenario tool. */\n says: string\n}\n\nexport interface StationGenerated {\n flow: StationFlow\n omitted: StationOmission[]\n}\n\nconst GOOD = 'good'\nconst SCRAP = 'scrap'\n\nexport function generateFlow(facts: StationFacts): StationGenerated {\n const steps: StationStep[] = []\n const omitted: StationOmission[] = []\n\n /*\n * Who is at the bench, first and always.\n *\n * Without it every later fact is unattributed — the counts belong to nobody, and a report filed\n * that way cannot answer the one question anybody asks it later.\n */\n steps.push({ id: 'who', kind: 'scan', label: '작업자' })\n\n /* Where am I, and on what. Only axes the facts actually support. */\n const reads = ['station.equipment']\n\n if (facts.locationName) {\n reads.push('location.name')\n }\n\n if (facts.hasJobOrders) {\n reads.push('order.name', 'materialDefinition.name')\n } else {\n omitted.push({\n code: 'text.omitted-order-fields',\n says: '작업지시 칸을 넣지 않았습니다 — 이 공정에 걸린 작업지시가 아직 없습니다'\n })\n }\n\n steps.push({ id: 'here', kind: 'show', label: '지금 이 자리', reads })\n\n /* The two counts. Both are `notable` on a production report, and neither has an automatic source. */\n steps.push({ id: GOOD, kind: 'enter', label: '양품 수' })\n steps.push({ id: SCRAP, kind: 'enter', label: '불량 수' })\n\n const report = facts.services.find(one => one.name === 'reportProduction')\n\n steps.push({ id: 'start', kind: 'run', label: '시작', action: 'start' })\n\n if (report) {\n steps.push({\n id: 'finish',\n action: 'finish',\n kind: 'run',\n label: '완료',\n service: report,\n arguments: wire(report.fields, { goodCount: GOOD, nonconformingCount: SCRAP })\n })\n\n /*\n * `materialActual` is left alone on purpose. What went in and how much is a recipe's business,\n * and guessing it from an equipment declaration would put a box on the screen that an operator\n * has to fill from memory.\n */\n if (report.fields.some(field => field.name === 'materialActual')) {\n omitted.push({\n code: 'text.omitted-material-actual',\n says: '자재 실적을 넣지 않았습니다 — 무엇이 얼마나 들어갔는지는 레시피가 정하는 것이라 설비 선언에서 끌어올 수 없습니다'\n })\n }\n } else {\n /* No production report service at all is a broken catalogue, not an ordinary omission. */\n omitted.push({\n code: 'text.omitted-no-report-service',\n says: '완료 단추가 아무것도 기록하지 않습니다 — reportProduction 을 카탈로그에서 못 찾았습니다'\n })\n\n steps.push({ id: 'finish', kind: 'run', label: '완료', action: 'finish' })\n }\n\n /* Stopping, and saying why. A pause button with no reason to give records nothing useful. */\n const stopping = facts.services.find(one => one.name === 'recordEquipmentStatePeriod')\n\n if (stopping && facts.stopReasons.length) {\n steps.push({\n id: 'pause',\n action: 'pause',\n kind: 'run',\n label: '일시정지',\n service: stopping,\n arguments: wire(stopping.fields, {})\n })\n } else if (!facts.stopReasons.length) {\n omitted.push({\n code: 'text.omitted-no-stop-reasons',\n says: '일시정지 단추를 넣지 않았습니다 — 이 공장에 정지 사유가 하나도 선언되어 있지 않습니다'\n })\n }\n\n /*\n * Inspection, and **only when there is one answer to which specification.**\n *\n * `specId` is required and there is no context axis for it — a bench does not carry \"the\n * current test spec\" the way it carries its own name. It comes from the equipment declaration,\n * which is known here and nowhere later, so it is written down as a fixed value.\n *\n * With two or more declared, no rule can pick. Generating the step anyway would hand over a\n * button that fails when pressed; generating it without `specId` would hand over the exact\n * defect this module exists to warn about. So it is left out and said out loud.\n *\n * This is why the generator is run end to end and not only unit tested: the first fixture here\n * had no `specId` in it, and every test was green while the real thing produced a broken step.\n */\n const inspection = facts.services.find(one => one.name === 'recordInspectionResult')\n const specs = facts.testSpecifications\n\n if (!specs.length) {\n omitted.push({\n code: 'text.omitted-no-test-spec',\n params: { equipment: facts.equipmentName },\n says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 붙어 있지 않습니다`\n })\n } else if (specs.length > 1) {\n omitted.push({\n code: 'text.omitted-many-test-specs',\n params: { equipment: facts.equipmentName, specs: specs.join(' · ') },\n says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 ${specs.length}개(${specs.join(' · ')})라 어느 것인지 규칙으로 정할 수 없습니다`\n })\n } else if (inspection) {\n /* 검사 기록 mutation을 보내는 런타임은 아직 없다. 실적으로 대신 기록하지 않는다. */\n omitted.push({\n code: 'text.omitted-no-inspection-runtime',\n says: '검사 단계를 넣지 않았습니다 — 검사 기록 실행 연결이 아직 구현되지 않았습니다'\n })\n }\n\n /* Not drawn. How often the screen re-reads — computed from how long the work takes. */\n steps.push({ id: 'settings', kind: 'settings', refreshSeconds: refreshFor(facts.operationDuration) })\n\n return { flow: { steps }, omitted }\n}\n\n/**\n * Fill in every argument that has one right answer, and point the rest at the steps we just made.\n *\n * A field with no suggestion and no step to take it from is left unset **on purpose** — that is\n * exactly the state `checkFlow` names, and inventing a value here would silence a warning by\n * making it untrue rather than by fixing it.\n */\nfunction wire(fields: StationServiceField[], fromSteps: Record<string, string>): Record<string, StationArgumentSource> {\n const wired: Record<string, StationArgumentSource> = {}\n\n for (const field of fields) {\n const entry = fromSteps[field.name]\n\n if (entry) {\n wired[field.name] = { from: 'input', stepId: entry }\n continue\n }\n\n const suggested = suggestSource(field.name)\n\n if (suggested) {\n wired[field.name] = suggested\n }\n }\n\n return wired\n}\n\n/**\n * How often to re-read, from how long the work takes.\n *\n * ── Why not one number for everybody ─────────────────────────────────────\n * A 30-second cut and a 392-second assembly are different screens. Reading every 5 seconds at the\n * assembly bench is forty pointless round trips per piece across a plant of twelve benches; reading\n * every 30 at the cutter means the count is a whole piece behind for most of the cycle.\n *\n * Roughly forty reads across one cycle, held between 5 and 60 seconds. The bounds matter more than\n * the ratio: below 5 the screen is a load generator, above 60 it looks frozen to somebody standing\n * in front of it.\n */\nexport function refreshFor(duration?: string): number {\n const seconds = secondsOf(duration)\n\n if (!seconds) {\n return 10\n }\n\n return Math.min(60, Math.max(5, Math.round(seconds / 40)))\n}\n\n/** `PT392S` · `PT7M30S` · `PT1H` → seconds. Anything else → nothing, and the caller uses its default. */\nfunction secondsOf(duration?: string): number | undefined {\n if (!duration) {\n return undefined\n }\n\n const said = /^PT(?:(\\d+(?:\\.\\d+)?)H)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)S)?$/.exec(duration.trim().toUpperCase())\n\n if (!said || (!said[1] && !said[2] && !said[3])) {\n return undefined\n }\n\n return Number(said[1] ?? 0) * 3600 + Number(said[2] ?? 0) * 60 + Number(said[3] ?? 0)\n}\n"]}
1
+ {"version":3,"file":"station-generate.js","sourceRoot":"","sources":["../../../server/service/station/station-generate.ts"],"names":[],"mappings":"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","sourcesContent":["import { suggestSource } from './station-argument-sources.js'\nimport type { StationArgumentSource, StationFlow, StationServiceField, StationStep } from './station-flow.js'\n\n/**\n * A station screen from what the factory already declares — **rules, not inference.**\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why a rule generator comes before an AI one\n *\n * Filling in a service's required fields is **arithmetic, not judgement**. The equipment is the\n * station's equipment; the operation is the operation; `status` is what the button means. There is\n * exactly one right answer and it is derivable, so a model that guesses it can only be right by\n * accident and wrong without saying so.\n *\n * What is left for a sentence in plain language — «검사도 같이 하는 자리다» — is which *services*\n * belong here. That is the part worth asking a person, or eventually a model, and it is a small\n * closed choice rather than an open one.\n *\n * ## What it leaves out is part of the output\n *\n * A generator that quietly omits the inspection step leaves the author thinking they were given\n * everything. They add it by hand, and it goes to the floor as a box that never fills, because the\n * equipment has no test specification behind it. So every omission is returned with its reason.\n *\n * ## It closes the loop it opens\n *\n * `goodCount` has no automatic source — a person types it. The generator is the one creating the\n * number pad, so it is also the one that can point the argument at it. A generator that produced\n * the pad and left the wiring undone would hand the author the exact defect this module was built\n * to warn about.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** What the factory says about this station. Gathered by the resolver; this module reads only. */\nexport interface StationFacts {\n equipmentName: string\n /** Where it stands. Drawn so an operator can tell one bench from the next. */\n locationName?: string\n /** The operation this bench runs, when the assignment names one. */\n operationKey?: string\n /** ISO 8601, e.g. `PT392S`. Decides how often the screen re-reads. */\n operationDuration?: string\n /** Test specifications the equipment carries. Empty means no inspection step. */\n testSpecifications: string[]\n /** Stop reasons this factory declares. Empty means a pause button with no reason to give. */\n stopReasons: string[]\n /** Whether any job order names this operation. Decides whether order fields are worth drawing. */\n hasJobOrders: boolean\n /** Every service a step could call, with its fields — the schema's own list. */\n services: { name: string; fields: StationServiceField[] }[]\n /**\n * The axes this product declared beyond the machine's core (`StationProduct.axes`).\n *\n * The generator draws a product's axis only when the product declared it. Drawn anyway, the flow it built\n * would be refused by the authoring check the moment it is saved — the generator's own output failing its gate.\n */\n axes?: readonly string[]\n}\n\n/** Something the generator did not put in, and why. The screen says it in the reader's language. */\nexport interface StationOmission {\n code: string\n params?: Record<string, string>\n /** The same thing in Korean, for logs and the scenario tool. */\n says: string\n}\n\nexport interface StationGenerated {\n flow: StationFlow\n omitted: StationOmission[]\n}\n\nconst GOOD = 'good'\nconst SCRAP = 'scrap'\n\nexport function generateFlow(facts: StationFacts): StationGenerated {\n const steps: StationStep[] = []\n const omitted: StationOmission[] = []\n\n /*\n * Who is at the bench, first and always.\n *\n * Without it every later fact is unattributed — the counts belong to nobody, and a report filed\n * that way cannot answer the one question anybody asks it later.\n */\n steps.push({ id: 'who', kind: 'scan', label: '작업자' })\n\n /* Where am I, and on what. Only axes the facts actually support. */\n const reads = ['station.equipment']\n\n if (facts.locationName) {\n reads.push('location.name')\n }\n\n if (facts.hasJobOrders) {\n reads.push('order.name')\n\n /* What is being made, when the product has a word for it. */\n if (facts.axes?.includes('materialDefinition.name')) reads.push('materialDefinition.name')\n } else {\n omitted.push({\n code: 'text.omitted-order-fields',\n says: '작업지시 칸을 넣지 않았습니다 — 이 공정에 걸린 작업지시가 아직 없습니다'\n })\n }\n\n steps.push({ id: 'here', kind: 'show', label: '지금 이 자리', reads })\n\n /* The two counts. Both are `notable` on a production report, and neither has an automatic source. */\n steps.push({ id: GOOD, kind: 'enter', label: '양품 수' })\n steps.push({ id: SCRAP, kind: 'enter', label: '불량 수' })\n\n const report = facts.services.find(one => one.name === 'reportProduction')\n\n steps.push({ id: 'start', kind: 'run', label: '시작', action: 'start' })\n\n if (report) {\n steps.push({\n id: 'finish',\n action: 'finish',\n kind: 'run',\n label: '완료',\n service: report,\n arguments: wire(report.fields, { goodCount: GOOD, nonconformingCount: SCRAP })\n })\n\n /*\n * `materialActual` is left alone on purpose. What went in and how much is a recipe's business,\n * and guessing it from an equipment declaration would put a box on the screen that an operator\n * has to fill from memory.\n */\n if (report.fields.some(field => field.name === 'materialActual')) {\n omitted.push({\n code: 'text.omitted-material-actual',\n says: '자재 실적을 넣지 않았습니다 — 무엇이 얼마나 들어갔는지는 레시피가 정하는 것이라 설비 선언에서 끌어올 수 없습니다'\n })\n }\n } else {\n /* No production report service at all is a broken catalogue, not an ordinary omission. */\n omitted.push({\n code: 'text.omitted-no-report-service',\n says: '완료 단추가 아무것도 기록하지 않습니다 — reportProduction 을 카탈로그에서 못 찾았습니다'\n })\n\n steps.push({ id: 'finish', kind: 'run', label: '완료', action: 'finish' })\n }\n\n /* Stopping, and saying why. A pause button with no reason to give records nothing useful. */\n const stopping = facts.services.find(one => one.name === 'recordEquipmentStatePeriod')\n\n if (stopping && facts.stopReasons.length) {\n steps.push({\n id: 'pause',\n action: 'pause',\n kind: 'run',\n label: '일시정지',\n service: stopping,\n arguments: wire(stopping.fields, {})\n })\n } else if (!facts.stopReasons.length) {\n omitted.push({\n code: 'text.omitted-no-stop-reasons',\n says: '일시정지 단추를 넣지 않았습니다 — 이 공장에 정지 사유가 하나도 선언되어 있지 않습니다'\n })\n }\n\n /*\n * Inspection, and **only when there is one answer to which specification.**\n *\n * `specId` is required and there is no context axis for it — a bench does not carry \"the\n * current test spec\" the way it carries its own name. It comes from the equipment declaration,\n * which is known here and nowhere later, so it is written down as a fixed value.\n *\n * With two or more declared, no rule can pick. Generating the step anyway would hand over a\n * button that fails when pressed; generating it without `specId` would hand over the exact\n * defect this module exists to warn about. So it is left out and said out loud.\n *\n * This is why the generator is run end to end and not only unit tested: the first fixture here\n * had no `specId` in it, and every test was green while the real thing produced a broken step.\n */\n const inspection = facts.services.find(one => one.name === 'recordInspectionResult')\n const specs = facts.testSpecifications\n\n if (!specs.length) {\n omitted.push({\n code: 'text.omitted-no-test-spec',\n params: { equipment: facts.equipmentName },\n says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 붙어 있지 않습니다`\n })\n } else if (specs.length > 1) {\n omitted.push({\n code: 'text.omitted-many-test-specs',\n params: { equipment: facts.equipmentName, specs: specs.join(' · ') },\n says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 ${specs.length}개(${specs.join(' · ')})라 어느 것인지 규칙으로 정할 수 없습니다`\n })\n } else if (inspection) {\n /* 검사 기록 mutation을 보내는 런타임은 아직 없다. 실적으로 대신 기록하지 않는다. */\n omitted.push({\n code: 'text.omitted-no-inspection-runtime',\n says: '검사 단계를 넣지 않았습니다 — 검사 기록 실행 연결이 아직 구현되지 않았습니다'\n })\n }\n\n /* Not drawn. How often the screen re-reads — computed from how long the work takes. */\n steps.push({ id: 'settings', kind: 'settings', refreshSeconds: refreshFor(facts.operationDuration) })\n\n return { flow: { steps }, omitted }\n}\n\n/**\n * Fill in every argument that has one right answer, and point the rest at the steps we just made.\n *\n * A field with no suggestion and no step to take it from is left unset **on purpose** — that is\n * exactly the state `checkFlow` names, and inventing a value here would silence a warning by\n * making it untrue rather than by fixing it.\n */\nfunction wire(fields: StationServiceField[], fromSteps: Record<string, string>): Record<string, StationArgumentSource> {\n const wired: Record<string, StationArgumentSource> = {}\n\n for (const field of fields) {\n const entry = fromSteps[field.name]\n\n if (entry) {\n wired[field.name] = { from: 'input', stepId: entry }\n continue\n }\n\n const suggested = suggestSource(field.name)\n\n if (suggested) {\n wired[field.name] = suggested\n }\n }\n\n return wired\n}\n\n/**\n * How often to re-read, from how long the work takes.\n *\n * ── Why not one number for everybody ─────────────────────────────────────\n * A 30-second cut and a 392-second assembly are different screens. Reading every 5 seconds at the\n * assembly bench is forty pointless round trips per piece across a plant of twelve benches; reading\n * every 30 at the cutter means the count is a whole piece behind for most of the cycle.\n *\n * Roughly forty reads across one cycle, held between 5 and 60 seconds. The bounds matter more than\n * the ratio: below 5 the screen is a load generator, above 60 it looks frozen to somebody standing\n * in front of it.\n */\nexport function refreshFor(duration?: string): number {\n const seconds = secondsOf(duration)\n\n if (!seconds) {\n return 10\n }\n\n return Math.min(60, Math.max(5, Math.round(seconds / 40)))\n}\n\n/** `PT392S` · `PT7M30S` · `PT1H` → seconds. Anything else → nothing, and the caller uses its default. */\nfunction secondsOf(duration?: string): number | undefined {\n if (!duration) {\n return undefined\n }\n\n const said = /^PT(?:(\\d+(?:\\.\\d+)?)H)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)S)?$/.exec(duration.trim().toUpperCase())\n\n if (!said || (!said[1] && !said[2] && !said[3])) {\n return undefined\n }\n\n return Number(said[1] ?? 0) * 3600 + Number(said[2] ?? 0) * 60 + Number(said[3] ?? 0)\n}\n"]}
@@ -21,14 +21,9 @@ function declareStationProduct(product) {
21
21
  if (missing.length) {
22
22
  throw new Error(`station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`);
23
23
  }
24
- const axes = product.axes ?? [];
25
- const shape = /^[a-z][A-Za-z0-9]*\.[a-z][A-Za-z0-9]*$/;
26
- const wrong = axes.filter((axis, at) => !shape.test(axis ?? '') || station_axes_js_1.STATION_AXES.includes(axis) || axes.indexOf(axis) !== at);
24
+ /* 규칙은 한 자리다 — 화면의 `registerStationAxes` 가 같은 것을 부른다(`shared/station-axes.js`). */
25
+ const wrong = (0, station_axes_js_1.wrongDeclaredAxes)(product.axes ?? []);
27
26
  if (wrong.length) {
28
- /*
29
- * A misspelt axis would pass every screen that reads it and stay blank on the bench; one the machine
30
- * already has is two owners of one name; a repeat is harmless but says the list was not read.
31
- */
32
27
  throw new Error(`station: ${product.product} 의 axes 선언이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +
33
28
  '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 선언하지 않습니다.');
34
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"station-product.js","sourceRoot":"","sources":["../../../server/service/station/station-product.ts"],"names":[],"mappings":"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","sourcesContent":["import { getMetadataStorage } from 'type-graphql'\nimport type { EntityManager } from 'typeorm'\nimport type { Domain } from '@things-factory/shell'\n\nimport type { StationAssignmentSituation } from './station-assignment-choice.js'\nimport type { StationServiceField } from './station-flow.js'\nimport { STATION_AXES } from './station-axes.js'\n\n/**\n * What a product has to say before the station machinery will run for it (ADR-0083 decision 2).\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why this file exists\n *\n * The station machinery is going to `@things-factory/station`, where operato-warehouse, and later\n * a yard and an energy product, will use the same authoring, the same flow model and the same\n * release approval. Nothing of that is about a factory. What *is* about a factory is small: which\n * values a screen may read, which mutations a step may call, who signs a release, and where those\n * values actually come from.\n *\n * This is that boundary, drawn **before** anything moves. Splitting while moving would leave the\n * first failure unattributable — was it the split or the move? — which is the order ADR-0078 used\n * when it lifted the outbox judgement out one layer at a time.\n *\n * ## Declared, never defaulted\n *\n * A product that has not declared gets an error naming what is missing, not an empty list. A\n * default here would mean a warehouse screen quietly offering a factory's mutations, or offering\n * none and reading as \"this product has no services\" — the silent empty this codebase keeps\n * finding. The same reason `registerWorkDoors` has no default door list.\n *\n * ## The assignment columns are the standard's general words (ADR-0083 decision 5)\n *\n * They used to be a factory's: `productionLineName` became `workCenterName` because the contract\n * already says `WorkCenter` is the general term over `ProcessCell` · `ProductionLine` ·\n * `ProductionUnit` · `StorageZone`. A warehouse zone and a yard block have somewhere to stand now.\n * `routeKey` and `operationKey` stay — a warehouse has a picking route too. No union of both\n * spellings: that would grow four products every time an axis is added.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** Something a station step can call. The product owns the list; the machinery reads the shape. */\nexport interface StationServiceDeclaration {\n /** The mutation name, exactly as the schema spells it. */\n name: string\n /** What an author is doing when they pick this — their words, not the mutation's. */\n describes: string\n /**\n * The `@InputType` whose fields this call takes — **when it takes one.**\n *\n * Absent, the machine reads the mutation's own arguments by `name` (ADR-0083 ruling ②): a warehouse's\n * floor doors take a task name and a scanned place, not one input object, and wrapping them in one would\n * give the same door two shapes.\n */\n input?: Function\n /**\n * Nullable fields that still must not be left empty.\n *\n * Absent means \"nobody wrote it down\", not zero — and the two are different facts on a\n * production report.\n */\n notable?: string[]\n}\n\n/**\n * The facts this product knows about one station, in the words assignment selects on.\n *\n * The machinery reads what the platform owns (the equipment and its classes) itself. This answers\n * only what the product's own domain knows — for a factory, which line it stands on and what the\n * order running there is made of.\n */\nexport type StationGroundReader = (\n manager: EntityManager,\n domain: Domain,\n stationName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string }\n) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>\n\n/**\n * 기계가 만든 변경 목록을 제품의 이력으로 보내는 자리 (ADR-0083 결정 7, 선언 다섯).\n *\n * **무엇이 바뀌었는지는 기계가 만든다** — 자기 엔티티를 자기가 안다. 화면 정의가 지워질 때 어느 칸이\n * 무엇이었는지는 `deletionChanges` 가 이 모듈 안에서 만든다.\n *\n * **그것이 어느 이력으로 가는지는 제품이 정한다.** plant 는 `fact-change` 로 보내고, 창고는 자기\n * 이력을 갖는다. 그 표와 그 낱말(`source` 같은 것)은 제품의 것이라 모듈이 알 까닭이 없다.\n *\n * 필수다. 선언하지 않으면 통제 문서를 지운 이력이 **오류 없이 안 남는다** — 통제 문서에서 그것은\n * 「누가 언제 무엇을 지웠나」를 못 답하는 것이고, 감사에서 드러날 때는 이미 늦다.\n */\nexport type StationChangeRecorder = (\n manager: EntityManager,\n domain: Domain,\n what: {\n factKind: string\n factName: string\n changes: { field: string; fromValue?: string; toValue?: string }[]\n at?: Date\n },\n by: {\n user?: unknown\n /** 결재를 지나 온 것이면 그 건. 누가 승인했는지는 워크리스트가 들고 이것으로 찾아간다. */\n activityInstanceId?: string\n }\n) => Promise<unknown>\n\nexport interface StationProduct {\n /** For the message when something is missing — the product says its own name. */\n product: string\n /**\n * The axes this product's screens read beyond the machine's own (ADR-0083 ruling ①) — `group.name`,\n * e.g. `unit.epc` · `task.kind`. The closed set a screen is checked against is the machine's list and\n * this. Absent means the product reads only the machine's.\n */\n axes?: readonly string[]\n services: StationServiceDeclaration[]\n readGround: StationGroundReader\n recordChange: StationChangeRecorder\n}\n\nlet declared: StationProduct | undefined\n\n/**\n * Called once at boot, from the product's own declaration file.\n *\n * 빠진 자리를 여기서 잡는다. 타입이 이미 다섯을 다 요구하지만 서버 쪽은 `strict: false` 로 돌고\n * 선언 파일에 `as any` 가 한 번 끼면 타입이 못 막는다. 그때 빠진 자리는 **부를 때**에야 드러나고,\n * 그 자리가 `recordChange` 면 통제 문서를 지운 이력이 오류 없이 안 남는다 — 기동 때 말하는 쪽이\n * 낫다(ADR-0083 결정 2 「선언은 필수이고 모듈에 기본값은 없다」).\n */\nexport function declareStationProduct(product: StationProduct): void {\n const missing = (['services', 'readGround', 'recordChange'] as const).filter(one => !product?.[one])\n\n if (missing.length) {\n throw new Error(\n `station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`\n )\n }\n\n const axes = product.axes ?? []\n const shape = /^[a-z][A-Za-z0-9]*\\.[a-z][A-Za-z0-9]*$/\n const wrong = axes.filter((axis, at) => !shape.test(axis ?? '') || STATION_AXES.includes(axis) || axes.indexOf(axis) !== at)\n\n if (wrong.length) {\n /*\n * A misspelt axis would pass every screen that reads it and stay blank on the bench; one the machine\n * already has is two owners of one name; a repeat is harmless but says the list was not read.\n */\n throw new Error(\n `station: ${product.product} 의 axes 선언이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +\n '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 선언하지 않습니다.'\n )\n }\n\n declared = product\n}\n\n/**\n * Every axis a screen of this product may read — the machine's and the product's (ADR-0083 ruling ①).\n * The authoring surface lists these; `checkFlow` refuses anything else.\n */\nexport function stationAxesOf(product: Pick<StationProduct, 'axes'>): string[] {\n return [...STATION_AXES, ...(product.axes ?? [])]\n}\n\n/**\n * What the product declared — **or an error saying it has not.**\n *\n * Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering\n * with an empty product instead would draw a screen with no services on it and no reason given.\n */\nexport function stationProduct(): StationProduct {\n if (!declared) {\n throw new Error(\n 'station: 이 제품이 선언을 하지 않았습니다 — 서비스 목록 · 바탕을 읽는 자리 · 기록을 남기는 자리를 declareStationProduct 로 등록해야 현장 화면이 섭니다.'\n )\n }\n\n return declared\n}\n\n/** Tests only — a declaration is meant to be made once, at boot. */\nexport function forgetStationProduct(): void {\n declared = undefined\n}\n\n/**\n * The fields of one service, as the running schema has them.\n *\n * Reads type-graphql's own metadata rather than issuing an introspection query: it is the same\n * declaration the schema is built from, it needs no round trip, and it works before the server\n * has finished starting.\n */\nexport function fieldsOfService(declared: StationServiceDeclaration): StationServiceField[] {\n const storage = getMetadataStorage()\n const notable = new Set(declared.notable ?? [])\n\n if (!declared.input) {\n return argumentsOfMutation(declared.name, notable)\n }\n\n /*\n * Fields are stored against the class, and a class that extends another has its parent's fields\n * under the parent's target. Walking the prototype chain keeps an inherited field from going\n * missing — which would look exactly like the schema not having it.\n */\n const targets = new Set<Function>()\n\n for (let at: Function | null = declared.input; at && at !== Function.prototype; at = Object.getPrototypeOf(at)) {\n targets.add(at)\n }\n\n return storage.fields\n .filter(field => targets.has(field.target as Function))\n .map(field => ({\n name: field.name,\n required: !field.typeOptions?.nullable,\n /*\n * The description the field's own owner wrote.\n *\n * `goodCount` on a screen is a raw schema name to a plant manager. The input type already\n * carries `@Field({ description })` for many of them — the person who added the field is the\n * one who can say what it means, and they already did. Carrying it through beats a second\n * table here that goes stale the day a field is added: a field with no description shows its\n * name, which is what happened anyway before.\n */\n ...(field.description ? { describes: field.description } : {}),\n ...(notable.has(field.name) ? { notable: true } : {})\n }))\n}\n\n/**\n * A door that takes no input object — **its own arguments, as the schema has them** (ADR-0083 ruling ②).\n *\n * The same metadata the schema is built from, read in the same place as the input-type fields, so a door's\n * arguments cannot drift from what the running server takes. `@Ctx()` and other non-argument parameters are\n * not fields an author fills.\n *\n * A name the schema does not have is said. Answered with no fields, a misspelt door would look like a door\n * that needs nothing, and the screen built on it would call something that is not there.\n */\nfunction argumentsOfMutation(name: string, notable: Set<string>): StationServiceField[] {\n const storage = getMetadataStorage()\n const mutation = storage.mutations.find(one => one.schemaName === name)\n\n if (!mutation) {\n throw new Error(`station: 선언한 문 ${name} 이(가) 스키마에 없습니다 — 이름이 틀렸거나 그 리졸버가 등록되지 않았습니다.`)\n }\n\n return storage.params\n .filter(param => param.kind === 'arg' && param.target === mutation.target && param.methodName === mutation.methodName)\n .sort((a, b) => a.index - b.index)\n .map(param => {\n const arg = param as Extract<typeof param, { kind: 'arg' }>\n return {\n name: arg.name,\n required: !arg.typeOptions?.nullable,\n ...(arg.description ? { describes: arg.description } : {}),\n ...(notable.has(arg.name) ? { notable: true } : {})\n }\n })\n}\n\n"]}
1
+ {"version":3,"file":"station-product.js","sourceRoot":"","sources":["../../../server/service/station/station-product.ts"],"names":[],"mappings":"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","sourcesContent":["import { getMetadataStorage } from 'type-graphql'\nimport type { EntityManager } from 'typeorm'\nimport type { Domain } from '@things-factory/shell'\n\nimport type { StationAssignmentSituation } from './station-assignment-choice.js'\nimport type { StationServiceField } from './station-flow.js'\nimport { STATION_AXES, wrongDeclaredAxes } from './station-axes.js'\n\n/**\n * What a product has to say before the station machinery will run for it (ADR-0083 decision 2).\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why this file exists\n *\n * The station machinery is going to `@things-factory/station`, where operato-warehouse, and later\n * a yard and an energy product, will use the same authoring, the same flow model and the same\n * release approval. Nothing of that is about a factory. What *is* about a factory is small: which\n * values a screen may read, which mutations a step may call, who signs a release, and where those\n * values actually come from.\n *\n * This is that boundary, drawn **before** anything moves. Splitting while moving would leave the\n * first failure unattributable — was it the split or the move? — which is the order ADR-0078 used\n * when it lifted the outbox judgement out one layer at a time.\n *\n * ## Declared, never defaulted\n *\n * A product that has not declared gets an error naming what is missing, not an empty list. A\n * default here would mean a warehouse screen quietly offering a factory's mutations, or offering\n * none and reading as \"this product has no services\" — the silent empty this codebase keeps\n * finding. The same reason `registerWorkDoors` has no default door list.\n *\n * ## The assignment columns are the standard's general words (ADR-0083 decision 5)\n *\n * They used to be a factory's: `productionLineName` became `workCenterName` because the contract\n * already says `WorkCenter` is the general term over `ProcessCell` · `ProductionLine` ·\n * `ProductionUnit` · `StorageZone`. A warehouse zone and a yard block have somewhere to stand now.\n * `routeKey` and `operationKey` stay — a warehouse has a picking route too. No union of both\n * spellings: that would grow four products every time an axis is added.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** Something a station step can call. The product owns the list; the machinery reads the shape. */\nexport interface StationServiceDeclaration {\n /** The mutation name, exactly as the schema spells it. */\n name: string\n /** What an author is doing when they pick this — their words, not the mutation's. */\n describes: string\n /**\n * The `@InputType` whose fields this call takes — **when it takes one.**\n *\n * Absent, the machine reads the mutation's own arguments by `name` (ADR-0083 ruling ②): a warehouse's\n * floor doors take a task name and a scanned place, not one input object, and wrapping them in one would\n * give the same door two shapes.\n */\n input?: Function\n /**\n * Nullable fields that still must not be left empty.\n *\n * Absent means \"nobody wrote it down\", not zero — and the two are different facts on a\n * production report.\n */\n notable?: string[]\n}\n\n/**\n * The facts this product knows about one station, in the words assignment selects on.\n *\n * The machinery reads what the platform owns (the equipment and its classes) itself. This answers\n * only what the product's own domain knows — for a factory, which line it stands on and what the\n * order running there is made of.\n */\nexport type StationGroundReader = (\n manager: EntityManager,\n domain: Domain,\n stationName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string }\n) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>\n\n/**\n * 기계가 만든 변경 목록을 제품의 이력으로 보내는 자리 (ADR-0083 결정 7, 선언 다섯).\n *\n * **무엇이 바뀌었는지는 기계가 만든다** — 자기 엔티티를 자기가 안다. 화면 정의가 지워질 때 어느 칸이\n * 무엇이었는지는 `deletionChanges` 가 이 모듈 안에서 만든다.\n *\n * **그것이 어느 이력으로 가는지는 제품이 정한다.** plant 는 `fact-change` 로 보내고, 창고는 자기\n * 이력을 갖는다. 그 표와 그 낱말(`source` 같은 것)은 제품의 것이라 모듈이 알 까닭이 없다.\n *\n * 필수다. 선언하지 않으면 통제 문서를 지운 이력이 **오류 없이 안 남는다** — 통제 문서에서 그것은\n * 「누가 언제 무엇을 지웠나」를 못 답하는 것이고, 감사에서 드러날 때는 이미 늦다.\n */\nexport type StationChangeRecorder = (\n manager: EntityManager,\n domain: Domain,\n what: {\n factKind: string\n factName: string\n changes: { field: string; fromValue?: string; toValue?: string }[]\n at?: Date\n },\n by: {\n user?: unknown\n /** 결재를 지나 온 것이면 그 건. 누가 승인했는지는 워크리스트가 들고 이것으로 찾아간다. */\n activityInstanceId?: string\n }\n) => Promise<unknown>\n\nexport interface StationProduct {\n /** For the message when something is missing — the product says its own name. */\n product: string\n /**\n * The axes this product's screens read beyond the machine's own (ADR-0083 ruling ①) — `group.name`,\n * e.g. `unit.epc` · `task.kind`. The closed set a screen is checked against is the machine's list and\n * this. Absent means the product reads only the machine's.\n */\n axes?: readonly string[]\n services: StationServiceDeclaration[]\n readGround: StationGroundReader\n recordChange: StationChangeRecorder\n}\n\nlet declared: StationProduct | undefined\n\n/**\n * Called once at boot, from the product's own declaration file.\n *\n * 빠진 자리를 여기서 잡는다. 타입이 이미 다섯을 다 요구하지만 서버 쪽은 `strict: false` 로 돌고\n * 선언 파일에 `as any` 가 한 번 끼면 타입이 못 막는다. 그때 빠진 자리는 **부를 때**에야 드러나고,\n * 그 자리가 `recordChange` 면 통제 문서를 지운 이력이 오류 없이 안 남는다 — 기동 때 말하는 쪽이\n * 낫다(ADR-0083 결정 2 「선언은 필수이고 모듈에 기본값은 없다」).\n */\nexport function declareStationProduct(product: StationProduct): void {\n const missing = (['services', 'readGround', 'recordChange'] as const).filter(one => !product?.[one])\n\n if (missing.length) {\n throw new Error(\n `station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`\n )\n }\n\n /* 규칙은 한 자리다 — 화면의 `registerStationAxes` 가 같은 것을 부른다(`shared/station-axes.js`). */\n const wrong = wrongDeclaredAxes(product.axes ?? [])\n\n if (wrong.length) {\n throw new Error(\n `station: ${product.product} 의 axes 선언이 틀렸습니다 — ${wrong.map(one => JSON.stringify(one)).join(' · ')}. ` +\n '축은 `무리.이름` 모양이고, 기계가 이미 가진 축은 다시 선언하지 않습니다.'\n )\n }\n\n declared = product\n}\n\n/**\n * Every axis a screen of this product may read — the machine's and the product's (ADR-0083 ruling ①).\n * The authoring surface lists these; `checkFlow` refuses anything else.\n */\nexport function stationAxesOf(product: Pick<StationProduct, 'axes'>): string[] {\n return [...STATION_AXES, ...(product.axes ?? [])]\n}\n\n/**\n * What the product declared — **or an error saying it has not.**\n *\n * Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering\n * with an empty product instead would draw a screen with no services on it and no reason given.\n */\nexport function stationProduct(): StationProduct {\n if (!declared) {\n throw new Error(\n 'station: 이 제품이 선언을 하지 않았습니다 — 서비스 목록 · 바탕을 읽는 자리 · 기록을 남기는 자리를 declareStationProduct 로 등록해야 현장 화면이 섭니다.'\n )\n }\n\n return declared\n}\n\n/** Tests only — a declaration is meant to be made once, at boot. */\nexport function forgetStationProduct(): void {\n declared = undefined\n}\n\n/**\n * The fields of one service, as the running schema has them.\n *\n * Reads type-graphql's own metadata rather than issuing an introspection query: it is the same\n * declaration the schema is built from, it needs no round trip, and it works before the server\n * has finished starting.\n */\nexport function fieldsOfService(declared: StationServiceDeclaration): StationServiceField[] {\n const storage = getMetadataStorage()\n const notable = new Set(declared.notable ?? [])\n\n if (!declared.input) {\n return argumentsOfMutation(declared.name, notable)\n }\n\n /*\n * Fields are stored against the class, and a class that extends another has its parent's fields\n * under the parent's target. Walking the prototype chain keeps an inherited field from going\n * missing — which would look exactly like the schema not having it.\n */\n const targets = new Set<Function>()\n\n for (let at: Function | null = declared.input; at && at !== Function.prototype; at = Object.getPrototypeOf(at)) {\n targets.add(at)\n }\n\n return storage.fields\n .filter(field => targets.has(field.target as Function))\n .map(field => ({\n name: field.name,\n required: !field.typeOptions?.nullable,\n /*\n * The description the field's own owner wrote.\n *\n * `goodCount` on a screen is a raw schema name to a plant manager. The input type already\n * carries `@Field({ description })` for many of them — the person who added the field is the\n * one who can say what it means, and they already did. Carrying it through beats a second\n * table here that goes stale the day a field is added: a field with no description shows its\n * name, which is what happened anyway before.\n */\n ...(field.description ? { describes: field.description } : {}),\n ...(notable.has(field.name) ? { notable: true } : {})\n }))\n}\n\n/**\n * A door that takes no input object — **its own arguments, as the schema has them** (ADR-0083 ruling ②).\n *\n * The same metadata the schema is built from, read in the same place as the input-type fields, so a door's\n * arguments cannot drift from what the running server takes. `@Ctx()` and other non-argument parameters are\n * not fields an author fills.\n *\n * A name the schema does not have is said. Answered with no fields, a misspelt door would look like a door\n * that needs nothing, and the screen built on it would call something that is not there.\n */\nfunction argumentsOfMutation(name: string, notable: Set<string>): StationServiceField[] {\n const storage = getMetadataStorage()\n const mutation = storage.mutations.find(one => one.schemaName === name)\n\n if (!mutation) {\n throw new Error(`station: 선언한 문 ${name} 이(가) 스키마에 없습니다 — 이름이 틀렸거나 그 리졸버가 등록되지 않았습니다.`)\n }\n\n return storage.params\n .filter(param => param.kind === 'arg' && param.target === mutation.target && param.methodName === mutation.methodName)\n .sort((a, b) => a.index - b.index)\n .map(param => {\n const arg = param as Extract<typeof param, { kind: 'arg' }>\n return {\n name: arg.name,\n required: !arg.typeOptions?.nullable,\n ...(arg.description ? { describes: arg.description } : {}),\n ...(notable.has(arg.name) ? { notable: true } : {})\n }\n })\n}\n\n"]}