import type { CommandTypeSpec } from '@operato/ops-contract'; /** 어느 주의에 어느 지시를 붙이는가 — 선언 한 줄. */ export interface ActuationRuleSpec { id: string; /** 이 트윈에만 적용된다. 트윈마다 라인이 다르므로 전역 규칙은 두지 않는다. */ instanceId: string; /** 어느 주의인가 — 커널의 안정 코드(`Attention.kind`). */ attentionKind: string; /** 이 심각도 이상일 때만 — 없으면 종류만 본다. */ minSeverity?: 'low' | 'medium' | 'high' | 'critical'; /** * **조건이 이만큼 이어졌을 때** 올린다. * * 없으면 처음 본 주기에 바로 올린다. 깜빡이는 조건에 현장 지시가 나가는 것을 막으려면 이 값이 필요하다 * — 커널이 조건이 처음 성립한 시각을 기억하므로(`Attention.timeStamp`) 여기서 잴 수 있다. */ minHeldMs?: number; /** 무엇을 내는가 — 선언된 조치 종류(`CommandTypeSpec.type`). 커널 자극이 아니다. */ commandType: string; /** * 지시의 내용 — `{이름}` 을 주의의 값으로 바꿔 넣는다. * * 바꿀 값은 `Attention.params` 와 `anchor` 에서 온다(`{locationId}` · `{ratioPct}`). 한 겹 중괄호를 * 쓰는 것은 이 저장소의 i18n 관례와 같다. * * 바꿀 것이 없으면 **그 자리를 비운다** — 지어낸 값을 넣지 않는다. 요구하는 칸이 비면 계약이 거절하고 * (§`missingCommandPayload`) 그 거절이 사람에게 전해진다. */ payloadTemplate?: Record; /** * **한 번 낸 근거에 다시 내기까지 기다리는 시간.** * * 이것이 없으면 승인하는 순간 규칙이 같은 것을 또 낸다 — 실제로 그랬다. 「결정을 기다리는 것」만 보고 * 중복을 막았는데, 승인하면 그 커맨드가 대기에서 빠지고 조건은 그대로 서 있기 때문이다. 사람이 * 승인할수록 대기 줄이 다시 차는 모양이 된다. * * 조건은 이어지는 것이고 조치는 시간이 걸린다. 낸 조치가 효과를 낼 때까지 기다리는 시간이 이 값이다. */ cooldownMs?: number; /** 꺼 둔 규칙은 판정하지 않는다. 지우지 않고 끌 수 있어야 한다. */ enabled?: boolean; } /** 판정에 들어가는 주의 — 커널이 낸 모양 그대로 중 필요한 것만. */ export interface AttentionLike { id: string; kind: string; severity: string; state?: string; anchor?: { locationId?: string; moverId?: string; orderId?: string; operation?: string; }; timeStamp?: string; params?: Record; } /** 올릴 것 하나. */ export interface RuleProposal { ruleId: string; attentionId: string; attentionKind: string; commandType: string; payload: Record; } /** 올리지 않은 것과 그 이유 — **말하지 않으면 사람은 규칙이 도는 줄 안다.** */ export interface RuleSkip { ruleId: string; attentionId?: string; reason: string; } export interface RuleVerdict { proposals: RuleProposal[]; /** 왜 안 올렸나. 조용히 넘어가지 않는다. */ skips: RuleSkip[]; /** 상한에 닿아 잘랐나 — 잘랐으면 몇 개를 두고 왔나. */ withheld?: number; } /** * 이번에 무엇을 올릴지 정한다 — **순수 함수**. 저장소도 시계도 보지 않는다. * * 시험이 재기동과 상한과 중복을 다 걸을 수 있어야 하고, 그러려면 이 판정이 밖에서 값을 받아야 한다. * 이 저장소에서 가장 자주 나는 결함이 「자리는 있고 길이 없다」이고, 게이트에 물리는 것이므로 여기서 * 나면 결과가 현장의 지시다. */ export declare function evaluateRules(args: { rules: readonly ActuationRuleSpec[]; attentions: readonly AttentionLike[]; /** 선언된 조치 종류 — 없는 종류를 가리키는 규칙은 여기서 걸린다. */ specs: readonly CommandTypeSpec[]; /** * **이미 다루고 있는 근거들** — 근거마다 마지막으로 조치를 낸 시각. * * 결정을 기다리는 것만 보면 부족하다. 승인하는 순간 그 커맨드가 대기에서 빠지고 조건은 그대로 * 서 있어서, 다음 바퀴가 같은 것을 또 낸다 — 걸어 보고 그 상태를 실제로 만들었다. * * 그래서 **낸 시각**을 받는다. 결정을 기다리는 것은 시각과 무관하게 막는다(§`openCauses`). * 저장소에서 읽어 넘긴다 — 프로세스 기억에 두면 재기동이 곧 중복이 된다. */ openCauses: ReadonlySet; /** 근거 → 마지막으로 조치를 낸 시각(ms). 없으면 낸 적이 없다. */ lastProposedAt?: ReadonlyMap; /** 지금 — 조건이 얼마나 이어졌는지 재는 기준. */ nowMs: number; /** 한 번에 몇 개까지 올리나. 넘는 것은 두고 오고, 두고 온 수를 말한다. */ maxPerRun?: number; }): RuleVerdict; /** 한 번에 올리는 상한 — 사람이 읽을 수 있는 수. 읽지 않는 대기 줄은 게이트가 아니다. */ export declare const DEFAULT_MAX_PER_RUN = 20; /** * 같은 근거에 다시 내기까지 — **선언하지 않아도 기다린다.** * * 기본을 0 으로 두면 선언하지 않은 규칙이 승인할 때마다 같은 것을 또 낸다. 그 결함을 기본값으로 두지 * 않는다. 더 빨리 내야 하는 규칙은 스스로 짧게 적는다. */ export declare const DEFAULT_COOLDOWN_MS: number; /** * 이 규칙이 낼 수 있는 종류인가 — **판정이 한 자리에만 있게.** * * 저장할 때와 돌 때가 같은 것을 봐야 한다. 두 벌이면 저장은 되는데 안 도는 규칙이 생기고, 사람은 규칙이 * 걸린 줄로 읽는다. * * `stimuli` 를 받는 이유: 커널 자극(`CMD`)을 적었을 때 **왜 안 되는지**를 말하려면 그 어휘를 알아야 한다. * 「선언되지 않았다」로만 답하면 부르는 쪽은 선언이 빠진 줄로 읽고 선언을 추가한다 — 그 순간 모델 안의 * 자극이 현장 지시가 된다. */ export declare function commandTypeGap(commandType: string, specs: readonly CommandTypeSpec[], stimuli?: readonly string[]): string | undefined;