ctx_driver 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +4 -0
- data/lib/ctx_driver/capture.rb +5 -3
- data/lib/ctx_driver/reader.rb +2 -9
- data/lib/ctx_driver/receipt.rb +17 -0
- data/lib/ctx_driver/session.rb +7 -0
- data/lib/ctx_driver/source.rb +61 -0
- data/lib/ctx_driver/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbf951e5ac7ad8b23db6a73c445dc84447409327d60ec5270198aba4bc7f328f
|
|
4
|
+
data.tar.gz: 495dddd8a7c80bd79db88ea28e4c05cc16903f284f035e283d7c999b3536846f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c00293dbe1c84597086e608f24c0cf7bf33b4e4cab329b62021501217afdb682035d59cdd66515c815860f7608038638778e5e69844f0d30358ee6d03bab17fb
|
|
7
|
+
data.tar.gz: 7d01f6e2c457b46d1889e34c7a79c05ab6d50d23624fddc46dd754abbd00adcab9f7b0b50fb9cb34249b2d428665915f5135a3b6651da138e409f7e9a0bff538
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ Changes to the public contract are recorded here. Versions follow Semantic Versi
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.5.0 (2026-10-30)
|
|
8
|
+
|
|
9
|
+
- Introduce immutable `Source` values with validated digest/byte pairs, timestamps and domain context. Retain acquired digests on clock failure. Share single-command receipt validation between reads and writes; borrowed configuration writes never reconnect.
|
|
10
|
+
|
|
7
11
|
## 0.4.0 (2026-10-29)
|
|
8
12
|
|
|
9
13
|
- Use the parser command contract for borrowed-session reads; preserve single-command identity and stop-after-desynchronization behavior.
|
data/README.md
CHANGED
|
@@ -123,3 +123,7 @@ MIT. See [LICENSE](LICENSE).
|
|
|
123
123
|
Receipt protocol: each completed step is `{ command: String }` with exactly the requested command bytes. Success requires one step; failure permits zero or one. Opaque placeholder steps are no longer accepted. Missing or mismatched identity yields `invalid_command_result` and stops later queries while retaining raw fingerprints. Session adapts native net-connector steps automatically.
|
|
124
124
|
|
|
125
125
|
本次使用 ctx_parser 0.4 系列契约;`Result` 新增 `covered_scopes`,单行命令统一由 `CtxParser::Commands.command_text` 校验。跨模块约定见 [ctx_runner 契约迁移](https://github.com/gatework/ctx_runner/blob/main/docs/contracts-2026-10-29.md)。
|
|
126
|
+
|
|
127
|
+
## Domain contract migration (2026-10-30)
|
|
128
|
+
|
|
129
|
+
Introduce immutable `Source` values with validated digest/byte pairs, timestamps and domain context. Retain acquired digests on clock failure. Share single-command receipt validation between reads and writes; borrowed configuration writes never reconnect. See the [schema 4 migration and module contracts](https://github.com/gatework/ctx_runner/blob/main/docs/contracts-2026-10-30.md). Regenerate old collection reports and change plans; changing only their version number is not supported.
|
data/lib/ctx_driver/capture.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "ctx_parser/immutable"
|
|
4
4
|
require_relative "command"
|
|
5
|
+
require_relative "source"
|
|
5
6
|
|
|
6
7
|
module CtxDriver
|
|
7
8
|
# 原始正文须显式读取;默认检查与序列化仅展示采集元数据。
|
|
@@ -15,7 +16,7 @@ module CtxDriver
|
|
|
15
16
|
unless started_at.is_a?(Time) && completed_at.is_a?(Time)
|
|
16
17
|
raise ArgumentError, "Capture timestamps must be Time values"
|
|
17
18
|
end
|
|
18
|
-
unless evidence.is_a?(Array) && evidence.all? { |entry| entry.is_a?(Hash) }
|
|
19
|
+
unless evidence.is_a?(Array) && evidence.all? { |entry| (entry.is_a?(Hash) || entry.is_a?(Source)) }
|
|
19
20
|
raise ArgumentError, "Capture evidence must be hashes"
|
|
20
21
|
end
|
|
21
22
|
raise ArgumentError, "Capture failure must be a hash or nil" unless failure.nil? || failure.is_a?(Hash)
|
|
@@ -28,7 +29,8 @@ module CtxDriver
|
|
|
28
29
|
@body = body.nil? || body.frozen? ? body : body.dup.freeze
|
|
29
30
|
@started_at = started_at.frozen? ? started_at : started_at.dup.freeze
|
|
30
31
|
@completed_at = completed_at.frozen? ? completed_at : completed_at.dup.freeze
|
|
31
|
-
@evidence
|
|
32
|
+
@evidence = evidence.map { |entry| Source.coerce(entry) }.freeze
|
|
33
|
+
@failure = CtxParser::Immutable.copy(failure)
|
|
32
34
|
@skipped = skipped
|
|
33
35
|
freeze
|
|
34
36
|
end
|
|
@@ -41,7 +43,7 @@ module CtxDriver
|
|
|
41
43
|
# 返回公开的数据结构,并保留状态与诊断信息。
|
|
42
44
|
def to_h
|
|
43
45
|
{ command: command, started_at: started_at, completed_at: completed_at,
|
|
44
|
-
evidence: evidence, failure: failure, skipped: skipped? }
|
|
46
|
+
evidence: evidence.map(&:to_h), failure: failure, skipped: skipped? }
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
# 序列化诊断元数据,不包含原始来源文本。
|
data/lib/ctx_driver/reader.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "time"
|
|
|
5
5
|
require_relative "capture"
|
|
6
6
|
require_relative "failure"
|
|
7
7
|
require_relative "session"
|
|
8
|
+
require_relative "receipt"
|
|
8
9
|
|
|
9
10
|
module CtxDriver
|
|
10
11
|
# 借用已打开会话逐条独立查询;连接归属与领域判断由外部负责。
|
|
@@ -66,14 +67,7 @@ module CtxDriver
|
|
|
66
67
|
raise TypeError, "Command output must be text" unless output.is_a?(String)
|
|
67
68
|
|
|
68
69
|
evidence = [ { command: command, sha256: Digest::SHA256.hexdigest(output), bytes: output.bytesize } ]
|
|
69
|
-
success = receipt
|
|
70
|
-
steps = receipt.steps
|
|
71
|
-
unless [ true, false ].include?(success) && steps.is_a?(Array) && (success ? steps.one? : steps.size <= 1)
|
|
72
|
-
raise TypeError, "One query must return one completed command"
|
|
73
|
-
end
|
|
74
|
-
unless steps.all? { |step| step.is_a?(Hash) && step[:command].is_a?(String) && step[:command].b == command.b }
|
|
75
|
-
raise TypeError, "Completed step must belong to the requested command"
|
|
76
|
-
end
|
|
70
|
+
success = Receipt.validate(receipt, command: command)
|
|
77
71
|
receipt_validated = true
|
|
78
72
|
if success
|
|
79
73
|
body = receipt.value!
|
|
@@ -96,7 +90,6 @@ module CtxDriver
|
|
|
96
90
|
evidence = [ evidence.first.merge(observed_at: observed_at) ]
|
|
97
91
|
end
|
|
98
92
|
rescue StandardError => error
|
|
99
|
-
evidence = []
|
|
100
93
|
command_failure ||= Failure.details(error, fallback: :invalid_command_result, stage: :response)
|
|
101
94
|
end
|
|
102
95
|
state_failure = detect_session_failure
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CtxDriver
|
|
4
|
+
# 读取与配置写入共用单命令回执身份,成功恰好对应一个完成步骤。
|
|
5
|
+
module Receipt
|
|
6
|
+
def self.validate(receipt, command:)
|
|
7
|
+
success, steps = receipt.success?, receipt.steps
|
|
8
|
+
unless [ true, false ].include?(success) && steps.is_a?(Array) && (success ? steps.one? : steps.size <= 1)
|
|
9
|
+
raise TypeError, "One query must return one completed command"
|
|
10
|
+
end
|
|
11
|
+
unless steps.all? { |step| step.is_a?(Hash) && step[:command].is_a?(String) && step[:command].b == command.b }
|
|
12
|
+
raise TypeError, "Completed step must belong to the requested command"
|
|
13
|
+
end
|
|
14
|
+
success
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/ctx_driver/session.rb
CHANGED
|
@@ -47,6 +47,13 @@ module CtxDriver
|
|
|
47
47
|
# 复用连接器的真实状态,避免失败后隐式重连。
|
|
48
48
|
def connected? = @session.connected?
|
|
49
49
|
|
|
50
|
+
# 配置写入使用连接器的模式提示符;借用已连接会话,不能触发隐式重连。
|
|
51
|
+
def write_command(command)
|
|
52
|
+
raise IOError, "Session is closed" unless connected?
|
|
53
|
+
command = Command.snapshot(command)
|
|
54
|
+
Response.new(@session.execute_command(command, output_sensitive: true))
|
|
55
|
+
end
|
|
56
|
+
|
|
50
57
|
# 单命令操作保护响应正文,并绑定当前完整提示符。
|
|
51
58
|
def send_commands(commands)
|
|
52
59
|
raise ArgumentError, "Expected one query" unless commands.is_a?(Array) && commands.one?
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ctx_parser/immutable"
|
|
4
|
+
require "ctx_parser/values"
|
|
5
|
+
require_relative "command"
|
|
6
|
+
|
|
7
|
+
module CtxDriver
|
|
8
|
+
# 来源身份与摘要是值;领域附加信息放入 context,不混入驱动字段。
|
|
9
|
+
class Source < Data.define(:command, :sha256, :bytes, :reference, :observed_at, :status, :context)
|
|
10
|
+
def self.coerce(value)
|
|
11
|
+
return value if value.is_a?(self)
|
|
12
|
+
raise ArgumentError, "Source must be a mapping" unless value.is_a?(Hash)
|
|
13
|
+
attributes = value.each_with_object({}) do |(key, entry), result|
|
|
14
|
+
unless (key.is_a?(String) || key.is_a?(Symbol)) && members.include?(key.to_sym) && !result.key?(key.to_sym)
|
|
15
|
+
raise ArgumentError, "Source contains unknown or duplicate fields"
|
|
16
|
+
end
|
|
17
|
+
result[key.to_sym] = entry
|
|
18
|
+
end
|
|
19
|
+
raise ArgumentError, "Invalid source status" if attributes.key?(:status) && attributes[:status].nil?
|
|
20
|
+
attributes[:status] = attributes[:status].to_sym if value.key?("status") && attributes[:status].is_a?(String)
|
|
21
|
+
new(**attributes)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(command: nil, sha256: nil, bytes: nil, reference: nil, observed_at: nil, status: nil, context: {})
|
|
25
|
+
command = Command.snapshot(command) unless command.nil?
|
|
26
|
+
unless sha256.nil? && bytes.nil? || sha256.is_a?(String) && /\A[0-9a-f]{64}\z/.match?(sha256) && bytes.is_a?(Integer) && bytes >= 0
|
|
27
|
+
raise ArgumentError, "Source requires a digest and nonnegative byte count together"
|
|
28
|
+
end
|
|
29
|
+
unless reference.nil? || reference.is_a?(String) && !reference.strip.empty? && !/[\x00-\x1f\x7f]/.match?(reference)
|
|
30
|
+
raise ArgumentError, "Invalid source reference"
|
|
31
|
+
end
|
|
32
|
+
raise ArgumentError, "Source requires an identity or digest" unless command || reference || sha256
|
|
33
|
+
unless status.nil? || %i[complete partial failed unsupported].include?(status)
|
|
34
|
+
raise ArgumentError, "Invalid source status"
|
|
35
|
+
end
|
|
36
|
+
raise ArgumentError, "Source context must be a mapping" unless context.is_a?(Hash)
|
|
37
|
+
observed_at = CtxParser::Values.timestamp(observed_at) if observed_at.is_a?(String)
|
|
38
|
+
raise ArgumentError, "Source time must be a Time or nil" unless observed_at.nil? || observed_at.is_a?(Time)
|
|
39
|
+
attributes = { command: command, sha256: sha256&.dup&.freeze, bytes: bytes, reference: reference&.dup&.freeze,
|
|
40
|
+
observed_at: observed_at&.getutc&.freeze, status: status, context: CtxParser::Immutable.copy(context) }
|
|
41
|
+
metadata = attributes.compact
|
|
42
|
+
metadata.delete(:context) if context.empty?
|
|
43
|
+
metadata[:observed_at] = attributes[:observed_at].iso8601(6).freeze if observed_at
|
|
44
|
+
@metadata = metadata.freeze
|
|
45
|
+
super(**attributes)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def verified? = !sha256.nil? && !bytes.nil?
|
|
49
|
+
def to_h = @metadata.dup
|
|
50
|
+
|
|
51
|
+
# 元数据映射访问沿用同一字段协议;所有更新重新经过构造校验。
|
|
52
|
+
def [](key) = @metadata[key]
|
|
53
|
+
def fetch(...) = @metadata.fetch(...)
|
|
54
|
+
def key?(key) = @metadata.key?(key)
|
|
55
|
+
def keys = @metadata.keys
|
|
56
|
+
def merge(**attributes) = with(**attributes)
|
|
57
|
+
def inspect = "#<#{self.class.name} verified=#{verified?}>"
|
|
58
|
+
alias to_s inspect
|
|
59
|
+
def pretty_print(printer) = printer.text(inspect)
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/ctx_driver/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ctx_driver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gatework contributors
|
|
@@ -76,7 +76,9 @@ files:
|
|
|
76
76
|
- lib/ctx_driver/command.rb
|
|
77
77
|
- lib/ctx_driver/failure.rb
|
|
78
78
|
- lib/ctx_driver/reader.rb
|
|
79
|
+
- lib/ctx_driver/receipt.rb
|
|
79
80
|
- lib/ctx_driver/session.rb
|
|
81
|
+
- lib/ctx_driver/source.rb
|
|
80
82
|
- lib/ctx_driver/version.rb
|
|
81
83
|
homepage: https://github.com/gatework/ctx_driver
|
|
82
84
|
licenses:
|