dsh-harbor-evolution 0.9.2 → 0.9.4

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.
@@ -3,6 +3,30 @@ import { localObjectDigest } from './interaction-objects.js'
3
3
 
4
4
  export const MAX_SELECTED_TRIALS = 1000
5
5
 
6
+ /** Validate persisted membership without renewing it or rerunning its original query. */
7
+ export function frozenTrialSelection(entry, ref, owner) {
8
+ const denied = () => { throw new Error('HARBOR_SELECTION_DENIED: Selection does not belong to this Session and Job.') }
9
+ if (!entry || entry.sessionId !== String(owner.sessionId) || entry.projectRoot !== owner.projectRoot || entry.workspace !== owner.workspace || entry.job !== ref.job || JSON.stringify(entry.ref) !== JSON.stringify(ref)) denied()
10
+ if (!['explicit', 'query-snapshot'].includes(entry.mode) || !/^sha256:[a-f0-9]{64}$/.test(entry.filterDigest ?? '') || !Array.isArray(entry.members) || !entry.members.length || entry.members.length > MAX_SELECTED_TRIALS || entry.members.length !== ref.selectionCount) denied()
11
+ const members = entry.members.map(member => {
12
+ if (typeof member.id !== 'string' || member.id.length > 240 || !/^(?:@?[\p{L}\p{N}][\p{L}\p{N}._:@+-]*|@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+)$/u.test(member.id) || !/^sha256:[a-f0-9]{64}$/.test(member.revision ?? '')) denied()
13
+ return { id: member.id, revision: member.revision }
14
+ })
15
+ if (new Set(members.map(member => member.id)).size !== members.length || localObjectDigest({ mode: entry.mode, filterDigest: entry.filterDigest, members }) !== ref.sourceDigest) denied()
16
+ return { ...entry, members }
17
+ }
18
+
19
+ /** Resolve the exact frozen members against current evidence; changes stay explicit. */
20
+ export function resolveFrozenTrialSelection(entry, ref, owner, currentTrials) {
21
+ const frozen = frozenTrialSelection(entry, ref, owner)
22
+ const byId = new Map(currentTrials.map(trial => [trial.id, trial]))
23
+ for (const member of frozen.members) {
24
+ const current = byId.get(member.id)
25
+ if (!current || localObjectDigest(current) !== member.revision) throw new Error('HARBOR_CONTEXT_STALE_SELECTION: Selected Trials changed. Reselect before continuing.')
26
+ }
27
+ return { ref: { ...frozen.ref }, value: { mode: frozen.mode, filterDigest: frozen.filterDigest, count: frozen.members.length, members: frozen.members, trials: frozen.members.map(member => byId.get(member.id)) } }
28
+ }
29
+
6
30
  /** Server-owned, frozen IDs/revisions; a query can never silently expand later. */
7
31
  export class TrialSelectionRegistry {
8
32
  constructor({ now = Date.now, ttlMs = 15 * 60_000, maxEntries = 256 } = {}) {
@@ -36,11 +60,6 @@ export class TrialSelectionRegistry {
36
60
  }
37
61
  resolve(ref, owner, currentTrials) {
38
62
  const entry = this.owned(ref, owner)
39
- const byId = new Map(currentTrials.map(trial => [trial.id, trial]))
40
- for (const member of entry.members) {
41
- const current = byId.get(member.id)
42
- if (!current || localObjectDigest(current) !== member.revision) throw new Error('HARBOR_CONTEXT_STALE_SELECTION: Selected Trials changed. Reselect before continuing.')
43
- }
44
- return { ref: { ...entry.ref }, value: { mode: entry.mode, filterDigest: entry.filterDigest, count: entry.members.length, members: entry.members, trials: entry.members.map(member => byId.get(member.id)) } }
63
+ return resolveFrozenTrialSelection(entry, ref, owner, currentTrials)
45
64
  }
46
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-harbor-evolution",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "DeepSeek Harness plugin and Skill for Harbor Candidate and Historical Session evaluation workflows.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -14,6 +14,7 @@
14
14
  "type": "object",
15
15
  "additionalProperties": false,
16
16
  "required": ["cwd_digest"],
17
+ "description": "Output workspace identity; source projects are recorded per Generation Record.",
17
18
  "properties": { "cwd_digest": { "$ref": "#/$defs/digest" } }
18
19
  },
19
20
  "selection": {
@@ -21,12 +22,31 @@
21
22
  "additionalProperties": false,
22
23
  "required": ["scope", "order", "requested_limit", "selected_count", "current_session_excluded"],
23
24
  "properties": {
24
- "scope": { "const": "exact-cwd" },
25
+ "scope": { "enum": ["exact-cwd", "dsh-history"] },
25
26
  "order": { "const": "last-activity-desc" },
26
27
  "requested_limit": { "type": "integer", "minimum": 1, "maximum": 10 },
27
28
  "selected_count": { "type": "integer", "minimum": 1, "maximum": 10 },
28
29
  "current_session_excluded": { "const": true },
29
- "created_after": { "type": "string", "format": "date-time" }
30
+ "created_after": { "type": "string", "format": "date-time" },
31
+ "scan": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "description": "Discovery window metadata. Activity ordering only applies to records read within this window, not unscanned history.",
35
+ "required": ["scope", "listed_count", "candidate_count", "read_count", "unscanned_count", "partial", "window_order", "selection_order"],
36
+ "properties": {
37
+ "scope": { "enum": ["exact-cwd", "dsh-history"] },
38
+ "listed_count": { "type": "integer", "minimum": 0 },
39
+ "candidate_count": { "type": "integer", "minimum": 0 },
40
+ "read_count": { "type": "integer", "minimum": 0 },
41
+ "unscanned_count": { "type": "integer", "minimum": 0 },
42
+ "partial": { "type": "boolean" },
43
+ "window_order": { "enum": ["all-candidates", "created-at-desc"] },
44
+ "selection_order": { "const": "last-activity-desc" }
45
+ },
46
+ "if": { "properties": { "scope": { "const": "exact-cwd" } } },
47
+ "then": { "properties": { "window_order": { "const": "all-candidates" } } },
48
+ "else": { "properties": { "window_order": { "const": "created-at-desc" } } }
49
+ }
30
50
  }
31
51
  },
32
52
  "source": {
@@ -62,6 +82,7 @@
62
82
  "source_ref": { "$ref": "#/$defs/digest" },
63
83
  "captured_through_seq": { "type": "integer", "minimum": 0 },
64
84
  "source_digest": { "$ref": "#/$defs/digest" },
85
+ "source_project_digest": { "$ref": "#/$defs/digest", "description": "Digest of the source Session project, when known; never inferred from the output workspace." },
65
86
  "observation_digest": { "$ref": "#/$defs/digest" },
66
87
  "last_activity_at": { "type": "string", "format": "date-time" },
67
88
  "generator": { "type": "object" },