@longtable/checkpoints 0.1.9
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/README.md +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/resolve-policy.d.ts +2 -0
- package/dist/resolve-policy.js +120 -0
- package/dist/runtime-guidance.d.ts +2 -0
- package/dist/runtime-guidance.js +167 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.js +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @longtable/checkpoints
|
|
2
|
+
|
|
3
|
+
Checkpoint policy and runtime guidance engine for LongTable.
|
|
4
|
+
|
|
5
|
+
Current responsibilities:
|
|
6
|
+
|
|
7
|
+
- structural and adaptive checkpoint resolution
|
|
8
|
+
- runtime guidance generation by interaction mode
|
|
9
|
+
- closure-disposition rules for provider adapters
|
|
10
|
+
- question-first governance for high-ambiguity research work
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const REQUIRED_LEVELS = new Set([
|
|
2
|
+
"universal_required",
|
|
3
|
+
"adaptive_required"
|
|
4
|
+
]);
|
|
5
|
+
function escalate(level) {
|
|
6
|
+
switch (level) {
|
|
7
|
+
case "none":
|
|
8
|
+
return "log_only";
|
|
9
|
+
case "log_only":
|
|
10
|
+
return "recommended";
|
|
11
|
+
case "recommended":
|
|
12
|
+
return "adaptive_required";
|
|
13
|
+
default:
|
|
14
|
+
return level;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function downgrade(level) {
|
|
18
|
+
switch (level) {
|
|
19
|
+
case "adaptive_required":
|
|
20
|
+
return "recommended";
|
|
21
|
+
case "recommended":
|
|
22
|
+
return "log_only";
|
|
23
|
+
case "log_only":
|
|
24
|
+
return "none";
|
|
25
|
+
default:
|
|
26
|
+
return level;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function pickPromptStyle(level) {
|
|
30
|
+
switch (level) {
|
|
31
|
+
case "universal_required":
|
|
32
|
+
return "structured_choice";
|
|
33
|
+
case "adaptive_required":
|
|
34
|
+
return "confirm_or_revise";
|
|
35
|
+
case "recommended":
|
|
36
|
+
return "advisory_summary";
|
|
37
|
+
default:
|
|
38
|
+
return "passive_log";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isHighStakeArtifact(artifactStakes) {
|
|
42
|
+
return artifactStakes === "study_protocol" || artifactStakes === "external_submission";
|
|
43
|
+
}
|
|
44
|
+
export function resolveCheckpointPolicy(profile, signal) {
|
|
45
|
+
let level = signal.baseLevel;
|
|
46
|
+
const rationale = [];
|
|
47
|
+
const unresolvedTensions = [
|
|
48
|
+
...(signal.unresolvedTensions ?? []),
|
|
49
|
+
...(signal.studyContract?.openTensions ?? [])
|
|
50
|
+
];
|
|
51
|
+
if (level === "universal_required") {
|
|
52
|
+
rationale.push("Universal checkpoints stay blocking across researcher profiles.");
|
|
53
|
+
}
|
|
54
|
+
if (!signal.studyContract && (signal.mode === "commit" || signal.mode === "submit")) {
|
|
55
|
+
rationale.push("No study contract is attached to this decision context.");
|
|
56
|
+
}
|
|
57
|
+
if (level === "adaptive_required" &&
|
|
58
|
+
(signal.mode === "explore" || signal.mode === "review" || signal.mode === "critique") &&
|
|
59
|
+
signal.artifactStakes === "private_note") {
|
|
60
|
+
level = "recommended";
|
|
61
|
+
rationale.push("Exploration on a private note should not hard-stop an adaptive checkpoint.");
|
|
62
|
+
}
|
|
63
|
+
if (profile.experienceLevel === "novice" &&
|
|
64
|
+
(signal.researchStage === "theory_selection" ||
|
|
65
|
+
signal.researchStage === "measurement_design" ||
|
|
66
|
+
signal.researchStage === "analysis_planning") &&
|
|
67
|
+
level !== "universal_required") {
|
|
68
|
+
level = escalate(level);
|
|
69
|
+
rationale.push("Novice researchers receive stronger guidance on high-risk research commitments.");
|
|
70
|
+
}
|
|
71
|
+
if (profile.forceCheckpointsOn?.includes(signal.checkpointKey) && level !== "universal_required") {
|
|
72
|
+
level = escalate(level);
|
|
73
|
+
rationale.push("The researcher profile explicitly asks for stronger checkpointing here.");
|
|
74
|
+
}
|
|
75
|
+
if (profile.relaxCheckpointsOn?.includes(signal.checkpointKey) &&
|
|
76
|
+
level === "adaptive_required" &&
|
|
77
|
+
signal.mode !== "commit" &&
|
|
78
|
+
signal.mode !== "submit") {
|
|
79
|
+
level = downgrade(level);
|
|
80
|
+
rationale.push("The researcher profile allows lighter intervention on this checkpoint.");
|
|
81
|
+
}
|
|
82
|
+
if (isHighStakeArtifact(signal.artifactStakes) && level !== "universal_required" && level !== "none") {
|
|
83
|
+
level = escalate(level);
|
|
84
|
+
rationale.push("High-stakes artifacts raise checkpoint intensity before information leaves private drafting.");
|
|
85
|
+
}
|
|
86
|
+
if (!signal.studyContract &&
|
|
87
|
+
signal.artifactStakes === "external_submission" &&
|
|
88
|
+
level !== "universal_required") {
|
|
89
|
+
level = "universal_required";
|
|
90
|
+
rationale.push("External submission without an attached study contract should remain blocking.");
|
|
91
|
+
}
|
|
92
|
+
if (unresolvedTensions.length > 0 &&
|
|
93
|
+
signal.mode === "commit" &&
|
|
94
|
+
level !== "universal_required" &&
|
|
95
|
+
level !== "none") {
|
|
96
|
+
level = escalate(level);
|
|
97
|
+
rationale.push("Unresolved tensions increase the need for explicit human commitment.");
|
|
98
|
+
}
|
|
99
|
+
if (profile.preferredCheckpointIntensity === "high" && level === "recommended") {
|
|
100
|
+
level = "adaptive_required";
|
|
101
|
+
rationale.push("Researcher preference is set to high checkpoint intensity.");
|
|
102
|
+
}
|
|
103
|
+
if (profile.preferredCheckpointIntensity === "low" &&
|
|
104
|
+
level === "recommended" &&
|
|
105
|
+
signal.mode !== "commit" &&
|
|
106
|
+
signal.mode !== "submit") {
|
|
107
|
+
level = "log_only";
|
|
108
|
+
rationale.push("Low checkpoint preference keeps non-blocking guidance lightweight.");
|
|
109
|
+
}
|
|
110
|
+
const blocking = REQUIRED_LEVELS.has(level);
|
|
111
|
+
return {
|
|
112
|
+
checkpointKey: signal.checkpointKey,
|
|
113
|
+
level,
|
|
114
|
+
blocking,
|
|
115
|
+
promptStyle: pickPromptStyle(level),
|
|
116
|
+
requiresDecisionLog: blocking || level === "recommended" || isHighStakeArtifact(signal.artifactStakes),
|
|
117
|
+
updateExplicitState: blocking || signal.mode === "commit" || signal.mode === "submit",
|
|
118
|
+
rationale
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { resolveCheckpointPolicy } from "./resolve-policy.js";
|
|
2
|
+
function unique(values) {
|
|
3
|
+
return [...new Set(values)];
|
|
4
|
+
}
|
|
5
|
+
function pushQuestion(questions, question) {
|
|
6
|
+
if (!questions.includes(question)) {
|
|
7
|
+
questions.push(question);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function createBaseGuidance(mode) {
|
|
11
|
+
switch (mode) {
|
|
12
|
+
case "explore":
|
|
13
|
+
return {
|
|
14
|
+
mode,
|
|
15
|
+
minimumQuestions: 2,
|
|
16
|
+
questionTypes: ["clarifying", "tension"],
|
|
17
|
+
mustAskBeforeClosure: true,
|
|
18
|
+
preferQuestionsFirst: true,
|
|
19
|
+
includeUnaskedQuestions: true,
|
|
20
|
+
includeOpenTensions: true,
|
|
21
|
+
includeWhyMayBeWrong: false,
|
|
22
|
+
preserveNarrativeTrace: false,
|
|
23
|
+
surfaceHumanCommitment: false,
|
|
24
|
+
closureDisposition: "delay",
|
|
25
|
+
mandatoryQuestions: [
|
|
26
|
+
"What ambiguity here would most change the research direction if resolved differently?",
|
|
27
|
+
"What tension is still unresolved in this framing?"
|
|
28
|
+
],
|
|
29
|
+
rationale: ["Explore mode delays closure and surfaces ambiguity before recommendation."]
|
|
30
|
+
};
|
|
31
|
+
case "review":
|
|
32
|
+
return {
|
|
33
|
+
mode,
|
|
34
|
+
minimumQuestions: 1,
|
|
35
|
+
questionTypes: ["clarifying", "boundary", "tension"],
|
|
36
|
+
mustAskBeforeClosure: false,
|
|
37
|
+
preferQuestionsFirst: false,
|
|
38
|
+
includeUnaskedQuestions: false,
|
|
39
|
+
includeOpenTensions: true,
|
|
40
|
+
includeWhyMayBeWrong: true,
|
|
41
|
+
preserveNarrativeTrace: false,
|
|
42
|
+
surfaceHumanCommitment: false,
|
|
43
|
+
closureDisposition: "tentative",
|
|
44
|
+
mandatoryQuestions: ["What assumption is doing the most work in the current structure?"],
|
|
45
|
+
rationale: ["Review mode should challenge assumptions before settling on synthesis."]
|
|
46
|
+
};
|
|
47
|
+
case "critique":
|
|
48
|
+
return {
|
|
49
|
+
mode,
|
|
50
|
+
minimumQuestions: 1,
|
|
51
|
+
questionTypes: ["boundary", "tension"],
|
|
52
|
+
mustAskBeforeClosure: false,
|
|
53
|
+
preferQuestionsFirst: false,
|
|
54
|
+
includeUnaskedQuestions: false,
|
|
55
|
+
includeOpenTensions: true,
|
|
56
|
+
includeWhyMayBeWrong: true,
|
|
57
|
+
preserveNarrativeTrace: false,
|
|
58
|
+
surfaceHumanCommitment: false,
|
|
59
|
+
closureDisposition: "delay",
|
|
60
|
+
mandatoryQuestions: ["What would have to be false for this conclusion to change?"],
|
|
61
|
+
rationale: ["Critique mode normalizes instability rather than smoothing it away."]
|
|
62
|
+
};
|
|
63
|
+
case "draft":
|
|
64
|
+
return {
|
|
65
|
+
mode,
|
|
66
|
+
minimumQuestions: 0,
|
|
67
|
+
questionTypes: [],
|
|
68
|
+
mustAskBeforeClosure: false,
|
|
69
|
+
preferQuestionsFirst: false,
|
|
70
|
+
includeUnaskedQuestions: false,
|
|
71
|
+
includeOpenTensions: false,
|
|
72
|
+
includeWhyMayBeWrong: false,
|
|
73
|
+
preserveNarrativeTrace: true,
|
|
74
|
+
surfaceHumanCommitment: false,
|
|
75
|
+
closureDisposition: "tentative",
|
|
76
|
+
mandatoryQuestions: [],
|
|
77
|
+
rationale: ["Draft mode may structure material, but it should preserve authorial trace."]
|
|
78
|
+
};
|
|
79
|
+
case "commit":
|
|
80
|
+
return {
|
|
81
|
+
mode,
|
|
82
|
+
minimumQuestions: 0,
|
|
83
|
+
questionTypes: ["boundary"],
|
|
84
|
+
mustAskBeforeClosure: false,
|
|
85
|
+
preferQuestionsFirst: false,
|
|
86
|
+
includeUnaskedQuestions: false,
|
|
87
|
+
includeOpenTensions: true,
|
|
88
|
+
includeWhyMayBeWrong: false,
|
|
89
|
+
preserveNarrativeTrace: false,
|
|
90
|
+
surfaceHumanCommitment: true,
|
|
91
|
+
closureDisposition: "strong",
|
|
92
|
+
mandatoryQuestions: ["What choice is the researcher personally committing to here?"],
|
|
93
|
+
rationale: ["Commit mode should make human responsibility explicit."]
|
|
94
|
+
};
|
|
95
|
+
case "submit":
|
|
96
|
+
return {
|
|
97
|
+
mode,
|
|
98
|
+
minimumQuestions: 0,
|
|
99
|
+
questionTypes: ["boundary"],
|
|
100
|
+
mustAskBeforeClosure: true,
|
|
101
|
+
preferQuestionsFirst: false,
|
|
102
|
+
includeUnaskedQuestions: false,
|
|
103
|
+
includeOpenTensions: true,
|
|
104
|
+
includeWhyMayBeWrong: true,
|
|
105
|
+
preserveNarrativeTrace: false,
|
|
106
|
+
surfaceHumanCommitment: true,
|
|
107
|
+
closureDisposition: "strongest",
|
|
108
|
+
mandatoryQuestions: [
|
|
109
|
+
"What claim or risk still requires explicit human responsibility before submission?"
|
|
110
|
+
],
|
|
111
|
+
rationale: ["Submit mode keeps final risks visible before external release."]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function addQuestionType(types, type) {
|
|
116
|
+
return unique([...types, type]);
|
|
117
|
+
}
|
|
118
|
+
export function resolveRuntimeGuidance(profile, signal, policy = resolveCheckpointPolicy(profile, signal)) {
|
|
119
|
+
const guidance = createBaseGuidance(signal.mode);
|
|
120
|
+
const unresolvedTensions = [
|
|
121
|
+
...(signal.unresolvedTensions ?? []),
|
|
122
|
+
...(signal.studyContract?.openTensions ?? [])
|
|
123
|
+
];
|
|
124
|
+
if (unresolvedTensions.length > 0) {
|
|
125
|
+
guidance.includeOpenTensions = true;
|
|
126
|
+
guidance.minimumQuestions = Math.max(guidance.minimumQuestions, 1);
|
|
127
|
+
guidance.questionTypes = addQuestionType(guidance.questionTypes, "tension");
|
|
128
|
+
guidance.rationale.push("Open tensions remain visible when a decision could still move.");
|
|
129
|
+
pushQuestion(guidance.mandatoryQuestions, "Which unresolved tension still matters enough to change the decision?");
|
|
130
|
+
}
|
|
131
|
+
if (!signal.studyContract && (signal.mode === "commit" || signal.mode === "submit")) {
|
|
132
|
+
guidance.minimumQuestions = Math.max(guidance.minimumQuestions, 1);
|
|
133
|
+
guidance.mustAskBeforeClosure = true;
|
|
134
|
+
guidance.questionTypes = addQuestionType(guidance.questionTypes, "clarifying");
|
|
135
|
+
guidance.rationale.push("Commitment without an attached study contract should force a clarifying question.");
|
|
136
|
+
pushQuestion(guidance.mandatoryQuestions, "What study contract commitment is still missing before this decision can be finalized?");
|
|
137
|
+
}
|
|
138
|
+
if (signal.artifactStakes === "external_submission") {
|
|
139
|
+
guidance.minimumQuestions = Math.max(guidance.minimumQuestions, 1);
|
|
140
|
+
guidance.mustAskBeforeClosure = true;
|
|
141
|
+
guidance.surfaceHumanCommitment = true;
|
|
142
|
+
guidance.includeWhyMayBeWrong = true;
|
|
143
|
+
guidance.closureDisposition = "strongest";
|
|
144
|
+
guidance.questionTypes = addQuestionType(guidance.questionTypes, "boundary");
|
|
145
|
+
guidance.rationale.push("External submission requires an explicit human responsibility check.");
|
|
146
|
+
pushQuestion(guidance.mandatoryQuestions, "What remaining risk would be unacceptable to release without direct human review?");
|
|
147
|
+
}
|
|
148
|
+
if (profile.humanAuthorshipSignal &&
|
|
149
|
+
(signal.mode === "draft" || signal.mode === "review" || signal.mode === "critique" || signal.researchStage === "writing")) {
|
|
150
|
+
guidance.preserveNarrativeTrace = true;
|
|
151
|
+
guidance.questionTypes = addQuestionType(guidance.questionTypes, "narrative");
|
|
152
|
+
guidance.rationale.push("The researcher profile asks LongTable to protect a recognizable human authorship signal.");
|
|
153
|
+
pushQuestion(guidance.mandatoryQuestions, `What in this output should preserve ${profile.humanAuthorshipSignal}?`);
|
|
154
|
+
}
|
|
155
|
+
if (policy.blocking && (signal.mode === "commit" || signal.mode === "submit")) {
|
|
156
|
+
guidance.minimumQuestions = Math.max(guidance.minimumQuestions, 1);
|
|
157
|
+
guidance.mustAskBeforeClosure = true;
|
|
158
|
+
guidance.surfaceHumanCommitment = true;
|
|
159
|
+
guidance.rationale.push("Blocking checkpoints require at least one explicit human-facing question before closure.");
|
|
160
|
+
}
|
|
161
|
+
if (signal.mode === "review" || signal.mode === "critique") {
|
|
162
|
+
guidance.includeWhyMayBeWrong = true;
|
|
163
|
+
}
|
|
164
|
+
guidance.questionTypes = unique(guidance.questionTypes);
|
|
165
|
+
guidance.mandatoryQuestions = unique(guidance.mandatoryQuestions);
|
|
166
|
+
return guidance;
|
|
167
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ArtifactStakes, CheckpointLevel, InteractionMode, PromptStyle, ResearchStage, StudyContract } from "@longtable/core";
|
|
2
|
+
export interface CheckpointSignal {
|
|
3
|
+
checkpointKey: string;
|
|
4
|
+
baseLevel: CheckpointLevel;
|
|
5
|
+
mode: InteractionMode;
|
|
6
|
+
artifactStakes: ArtifactStakes;
|
|
7
|
+
researchStage: ResearchStage;
|
|
8
|
+
unresolvedTensions?: string[];
|
|
9
|
+
studyContract?: StudyContract;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvedCheckpointPolicy {
|
|
12
|
+
checkpointKey: string;
|
|
13
|
+
level: CheckpointLevel;
|
|
14
|
+
blocking: boolean;
|
|
15
|
+
promptStyle: PromptStyle;
|
|
16
|
+
requiresDecisionLog: boolean;
|
|
17
|
+
updateExplicitState: boolean;
|
|
18
|
+
rationale: string[];
|
|
19
|
+
}
|
|
20
|
+
export type { ArtifactStakes, ClosureDisposition, CheckpointLevel, GuidanceQuestionType, InteractionMode, PromptStyle, ResearcherProfile, ResearchStage, RuntimeGuidance, StudyContract } from "@longtable/core";
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longtable/checkpoints",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Checkpoint policy engine for LongTable",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@longtable/core": "0.1.9"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.6.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"longtable",
|
|
31
|
+
"research",
|
|
32
|
+
"checkpoints",
|
|
33
|
+
"governance"
|
|
34
|
+
],
|
|
35
|
+
"author": "Hosung You",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/HosungYou/LongTable.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/HosungYou/LongTable#readme",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|