invoance 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 465e862ed29dc6dc0d5c4829472f739ddbaaace820845dd7c54bb6d9aabd77dc
4
- data.tar.gz: 49e142ab37c383cd94b14aaa6f02a456e7a190bbdd961de45d9f4e215febe56c
3
+ metadata.gz: 5e2d35b71291d29aa248a149572823d79d968ed379130b206e2e60776fe892b9
4
+ data.tar.gz: 544522d6d2e329196e3586604cf4ffc86d7bcd8bd8fc183ae7827ce077b2793f
5
5
  SHA512:
6
- metadata.gz: 6e26cf457a2f054a6d027c30a2fcb3b6335b220431de8d4b67541c6be824287a934ddc5b4509017602fc2949240489c7760fd85b4b024b00bbe609c82e43c078
7
- data.tar.gz: ca148fd01bef837bc515093332b3908866a6ce6ebbae7ba02cc97bb84ca33e0869655d128681895d767a2b339ce130d302c55cb21b6c024423aadf64814400a1
6
+ metadata.gz: f2caf663d660ce5ce79deadff7a86d83e9cc2d3c20fc861c3b558bc8da24fcb776f6968e70e0481a1c9ae6e2e239afe9b8e3b00406ce4c83cdbe341bddce2b76
7
+ data.tar.gz: 75a63ff16bc063470049c9f24ff8195916cfdaaad90d87d28fda40ebbd96137bfd7ebc170bbf97a9b4284a7923c2a93fea0db67be3c378b5f6e60195181475f3
data/CHANGELOG.md CHANGED
@@ -5,6 +5,38 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-07-14
9
+
10
+ ### Changed
11
+
12
+ - `Client#validate` now probes the scope-free introspection endpoint
13
+ `GET /v1/me` instead of `GET /v1/events?limit=1`. Keys restricted to
14
+ `audit:*` scopes now validate correctly (the old events probe could 403
15
+ on a missing events scope and report a misleading reason). The return
16
+ shape (`"valid"` / `"reason"` / `"base_url"`) and the 401 / 403 / 429 /
17
+ network classifications are unchanged; only the 403 `reason` text changed —
18
+ it now describes an IP-access-rule block instead of a missing events
19
+ permission, since `/v1/me` requires no scope.
20
+
21
+ ### Added
22
+
23
+ - `Client#me` — API-key introspection via `GET /v1/me`. Returns the raw
24
+ decoded response (organization, tenant, api_key with scopes, limits) as a
25
+ string-keyed `Hash`; raises like any other resource call.
26
+ - Audit org lifecycle methods on `audit.orgs`:
27
+ - `update(organization_id, name:)` — rename an org via
28
+ `PATCH /audit/orgs/:org_id`; pass `name: nil` to clear the name.
29
+ - `archive(organization_id)` / `unarchive(organization_id)` — idempotent
30
+ `POST /audit/orgs/:org_id/archive` and `.../unarchive`. Archiving freezes
31
+ new activity while history stays verifiable.
32
+ - `delete(organization_id)` — hard delete via `DELETE /audit/orgs/:org_id`;
33
+ raises `ConflictError` (`org_not_deletable`) when signed history would be
34
+ destroyed.
35
+ - `audit.orgs.list(include_archived: true)` — include archived orgs in
36
+ listings (excluded by default). Org responses now carry `archived_at`
37
+ (RFC 3339 string or `nil`).
38
+ - `PATCH` support in the HTTP transport.
39
+
8
40
  ## [0.1.0] - 2026-07-06
9
41
 
10
42
  ### Added
data/README.md CHANGED
@@ -53,6 +53,10 @@ puts event["event_id"]
53
53
  # Verify the API key / connectivity (never raises)
54
54
  result = client.validate
55
55
  raise "bad config: #{result["reason"]}" unless result["valid"]
56
+
57
+ # Introspect the key — org, tenant, scopes, limits (GET /v1/me; raises on error)
58
+ whoami = client.me
59
+ puts whoami["api_key"]["scopes"]
56
60
  ```
57
61
 
58
62
  Responses are plain Ruby `Hash`es with **string keys** matching the wire JSON
@@ -53,14 +53,23 @@ module Invoance
53
53
  @config.base_url
54
54
  end
55
55
 
56
- # Probe a cheap authenticated endpoint (GET /v1/events?limit=1) to
57
- # confirm the API key works. NEVER raises.
56
+ # Introspect the API key via GET /v1/me. Requires no scope, so it works
57
+ # for any valid key. Raises like every other resource call.
58
+ #
59
+ # @return [Hash] { "valid" =>, "organization" =>, "tenant" =>,
60
+ # "api_key" =>, "limits" => } (string keys, wire JSON as-is)
61
+ def me
62
+ @http.get("/me")
63
+ end
64
+
65
+ # Call the scope-free introspection endpoint (GET /v1/me) to confirm
66
+ # the API key works. NEVER raises.
58
67
  #
59
68
  # @return [Hash] { "valid" =>, "reason" =>, "base_url" => }
60
69
  def validate
61
70
  base = @config.base_url
62
71
  begin
63
- @events.list(limit: 1)
72
+ me
64
73
  { "valid" => true, "reason" => nil, "base_url" => base }
65
74
  rescue AuthenticationError
66
75
  { "valid" => false,
@@ -68,7 +77,7 @@ module Invoance
68
77
  "base_url" => base }
69
78
  rescue ForbiddenError
70
79
  { "valid" => true,
71
- "reason" => "API key authenticated but lacks permission to list events",
80
+ "reason" => "API key authenticated but the request was blocked (IP access rules)",
72
81
  "base_url" => base }
73
82
  rescue QuotaExceededError
74
83
  { "valid" => true,
data/lib/invoance/http.rb CHANGED
@@ -59,6 +59,16 @@ module Invoance
59
59
  handle(resp, ctx)
60
60
  end
61
61
 
62
+ # PATCH returning parsed JSON.
63
+ def patch(path, body = nil)
64
+ uri = build_uri(path)
65
+ ctx = { method: "PATCH", path: path }
66
+ req = Net::HTTP::Patch.new(uri)
67
+ req.body = JSON.generate(body) unless body.nil?
68
+ resp = do_request(req, uri, ctx, headers: @base_headers)
69
+ handle(resp, ctx)
70
+ end
71
+
62
72
  # DELETE returning parsed JSON.
63
73
  def delete(path)
64
74
  uri = build_uri(path)
@@ -107,9 +107,36 @@ module Invoance
107
107
  @http.post("/audit/orgs", body)
108
108
  end
109
109
 
110
- # GET /audit/orgs
111
- def list
112
- @http.get("/audit/orgs")
110
+ # GET /audit/orgs — archived orgs are excluded unless
111
+ # include_archived: true is passed.
112
+ def list(include_archived: nil)
113
+ @http.get("/audit/orgs", { "include_archived" => include_archived })
114
+ end
115
+
116
+ # PATCH /audit/orgs/:org_id — rename. Pass name: nil to clear the
117
+ # name (sends JSON null).
118
+ def update(organization_id, name:)
119
+ @http.patch("/audit/orgs/#{organization_id}", { "name" => name })
120
+ end
121
+
122
+ # POST /audit/orgs/:org_id/archive — idempotent. Freezes new
123
+ # activity (ingest/streams/portal/exports return 409 org_archived);
124
+ # history stays verifiable.
125
+ def archive(organization_id)
126
+ @http.post("/audit/orgs/#{organization_id}/archive")
127
+ end
128
+
129
+ # POST /audit/orgs/:org_id/unarchive — idempotent.
130
+ def unarchive(organization_id)
131
+ @http.post("/audit/orgs/#{organization_id}/unarchive")
132
+ end
133
+
134
+ # DELETE /audit/orgs/:org_id — hard delete, allowed only when
135
+ # nothing signed would be destroyed (never-ingested org, or
136
+ # archived + retention fully purged). Raises ConflictError
137
+ # (org_not_deletable) otherwise.
138
+ def delete(organization_id)
139
+ @http.delete("/audit/orgs/#{organization_id}")
113
140
  end
114
141
 
115
142
  # GET /audit/orgs/:org_id/integrity
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Invoance
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invoance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoance, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
11
+ date: 2026-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake