activeadmin_annotations 0.1.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/CODE_OF_CONDUCT.md +31 -0
  4. data/CONTRIBUTING.md +29 -0
  5. data/GOVERNANCE.md +29 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +321 -0
  8. data/SECURITY.md +27 -0
  9. data/activeadmin_annotations.gemspec +68 -0
  10. data/admin/annotation_reviews.rb +102 -0
  11. data/admin/annotations.rb +146 -0
  12. data/app/assets/controllers/activeadmin_annotations/annotator_controller.js +483 -0
  13. data/app/assets/controllers/activeadmin_annotations/clipboard_controller.js +73 -0
  14. data/app/assets/stylesheets/activeadmin_annotations.css +362 -0
  15. data/app/helpers/active_admin/annotations/panel_helper.rb +95 -0
  16. data/app/models/active_admin/annotations/annotation.rb +37 -0
  17. data/app/models/active_admin/annotations/application_record.rb +13 -0
  18. data/app/models/active_admin/annotations/review.rb +54 -0
  19. data/app/services/active_admin/annotations/review_service.rb +133 -0
  20. data/app/views/active_admin/annotations/_copy_details_action.html.erb +21 -0
  21. data/app/views/active_admin/annotations/_panel.html.erb +98 -0
  22. data/app/views/active_admin/annotations/_read_only_content.html.erb +3 -0
  23. data/config/polyrun_coverage.yml +8 -0
  24. data/db/migrate/20260617120000_create_activeadmin_annotations_tables.rb +41 -0
  25. data/db/migrate/20260706150000_add_metadata_json_gin_index_to_annotation_reviews.rb +9 -0
  26. data/db/migrate/20260710120000_add_panel_query_index_to_annotation_spans.rb +10 -0
  27. data/lib/activeadmin/annotations/annotatable.rb +15 -0
  28. data/lib/activeadmin/annotations/categories.rb +13 -0
  29. data/lib/activeadmin/annotations/content_revision.rb +101 -0
  30. data/lib/activeadmin/annotations/context.rb +11 -0
  31. data/lib/activeadmin/annotations/copy_text.rb +98 -0
  32. data/lib/activeadmin/annotations/engine.rb +49 -0
  33. data/lib/activeadmin/annotations/exporter.rb +99 -0
  34. data/lib/activeadmin/annotations/review_access.rb +29 -0
  35. data/lib/activeadmin/annotations/review_panel.rb +145 -0
  36. data/lib/activeadmin/annotations/version.rb +7 -0
  37. data/lib/activeadmin/annotations.rb +37 -0
  38. data/lib/activeadmin_annotations.rb +13 -0
  39. data/sig/activeadmin/annotations.rbs +5 -0
  40. metadata +393 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 27efb5276816339c66980cb3e77752ab208f0a3f25ac290b051957f48542b489
4
+ data.tar.gz: 8367a3f0d88f6ad69e2c18721f1576a0ed8e2027ede89a20d64edfa252b238a6
5
+ SHA512:
6
+ metadata.gz: aaf877c12d55510f4713004c8716fa87e0ab16c32cdd459e2ea3a5e070faeb7ecb4fc7a7969c1b40a499499ebba5c091c3944ca1d1f0ccbfe21ebc9d969531d9
7
+ data.tar.gz: 13991c461e4965f4e86800ea9585ae548ab36a69b64ea9878d86288aeb63a3710c5b0371b72b3bfc3451c59defc24719e941b49a8f84f90650af336820b58c1e
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.1.0 (2026-07-10)
4
+
5
+ - Add span-level annotations inside a bounded ActiveAdmin block.
6
+ - Add review and span models with JSONL export from the reviews index.
7
+ - Add Stimulus annotator and clipboard controllers with panel helper `activeadmin_annotations_panel`.
8
+ - Add `ActiveAdmin::Annotations::ReviewPanel` for revision pinning, stale banners, and advance-to-latest review flows.
9
+ - Add optional content revision pinning (`:auto`, `:active_version`, custom resolvers).
10
+ - Add `ActiveAdmin::Annotations::Annotatable` concern for host models.
11
+ - Add configurable review admin menu visibility via `review_menu_visible`.
12
+ - Add optional annotation categories and copy instructions via initializer settings.
13
+ - Add composite index on `annotation_spans` for panel load by review, field, and revision.
14
+ - Add PostgreSQL GIN index on review `metadata_json` for follow-up filtering.
@@ -0,0 +1,31 @@
1
+ # Code of Conduct
2
+
3
+ ## Our pledge
4
+
5
+ We pledge to make participation in our community a harassment-free experience for everyone. We operate on principles of mutual respect, privacy, and authentic engagement. We value substantive contributions and clarity on intentions.
6
+
7
+ ## Our standards
8
+
9
+ Examples of behavior that contributes to a positive environment include:
10
+
11
+ - Authenticity: engaging with genuine curiosity and admitting uncertainty rather than feigning knowledge.
12
+ - Responsible innovation: taking full responsibility for any content or code contributed, whether manually written or generated by automation tools.
13
+ - Gentle correction: responding politely to errors. We view mistakes as opportunities for learning, provided they are addressed with humility.
14
+ - Inclusive language: using language that welcomes diverse perspectives and respects the privacy and identity of all participants.
15
+
16
+ Examples of unacceptable behavior include:
17
+
18
+ - Harassment: public or private harassment, trolling, or insulting comments.
19
+ - Weaponized complexity: using jargon or overwhelming volume (including automated spam) to silence others.
20
+ - Publishing private information: sharing others' data or personal context without explicit permission.
21
+
22
+ ## Artificial intelligence and automation
23
+
24
+ In accordance with our commitment to collective awareness:
25
+
26
+ - Contributors are responsible for the accuracy and security of any AI-generated artifacts they submit.
27
+ - "The AI wrote it" is not a valid excuse for introducing bugs, security vulnerabilities, or bias.
28
+
29
+ ## Enforcement
30
+
31
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@kiskolabs.com. All complaints will be reviewed and investigated promptly and fairly.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,29 @@
1
+ # Contributing Guidelines
2
+
3
+ Thank you for your interest in contributing to activeadmin_annotations. We value learning over perfection but require rigor and responsibility.
4
+
5
+ ## The golden rule of automation
6
+
7
+ We welcome the use of AI and automation tools to reduce toil, but you must strictly adhere to the following:
8
+
9
+ 1. You are the author. You act as the responsible agent for any code you submit. You must review, debug, and understand every line.
10
+ 2. Manage cognitive load. Do not submit massive, unreviewed automated dumps. Respect the reviewers' time by annotating complex logic.
11
+ 3. Security. Never feed project secrets or private context into public AI models.
12
+
13
+ ## How to contribute
14
+
15
+ ### Reporting issues
16
+
17
+ - Verify accuracy before posting. Avoid generalizations.
18
+ - Use issue templates to provide clear goals, constraints, and reproduction steps.
19
+
20
+ ### Pull request process
21
+
22
+ - Scope: keep PRs focused on a single goal.
23
+ - Context: explain why the change is necessary.
24
+ - Testing: run `make test` locally before opening a PR.
25
+
26
+ ### Review process
27
+
28
+ - We encourage productive friction. Expect questions about your approach.
29
+ - If a reviewer suggests a change, view it as mutual aid, not criticism.
data/GOVERNANCE.md ADDED
@@ -0,0 +1,29 @@
1
+ # Project Governance
2
+
3
+ ## Overview
4
+
5
+ This project balances individual autonomy with collective coordination. We aim for a workflow that supports rapid iteration while maintaining strict guardrails for safety and architectural integrity.
6
+
7
+ ## Roles
8
+
9
+ ### Contributors
10
+
11
+ Anyone who submits code, documentation, or participates in discussions. Contributors are expected to exercise direct action—taking ownership of problems they identify—while adhering to our quality standards.
12
+
13
+ ### Maintainers (Human Oversight)
14
+
15
+ Maintainers are responsible for:
16
+
17
+ 1. Strategic judgment: defining scope and architectural direction.
18
+ 2. Review: verifying that contributions (human or automated) meet security and logic standards.
19
+ 3. Consensus building: facilitating decisions when the community is divided.
20
+
21
+ ## Decision making process
22
+
23
+ ### Lazy consensus
24
+
25
+ For most routine changes, we operate on lazy consensus. If a proposal is made and no objections are raised within two weeks, it is considered approved.
26
+
27
+ ### Strategic alignment
28
+
29
+ Major architectural changes, high-risk automation integrations, or changes that affect business logic require explicit approval from the maintainers. We prioritize substance over performance—a change must solve a real problem, not just appear polished.
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 amkisko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,321 @@
1
+ # activeadmin_annotations
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/activeadmin_annotations.svg)](https://badge.fury.io/rb/activeadmin_annotations) [![Test Status](https://github.com/amkisko/activeadmin_annotations.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/activeadmin_annotations.rb/actions/workflows/test.yml)
4
+
5
+ Collect span-level text highlights and comments inside a bounded ActiveAdmin block.
6
+
7
+ The gem stays domain-agnostic: the host app chooses what review context means, which optional categories to offer, where to render the panel, and whether annotations are pinned to content revisions.
8
+
9
+ ## Requirements
10
+
11
+ - Ruby 3.2+
12
+ - Rails 7.1+
13
+ - ActiveAdmin 3.0+
14
+ - PostgreSQL recommended for production (SQLite works for development; some metadata filters use Postgres JSON operators)
15
+
16
+ ## Host app setup
17
+
18
+ Subject models and the reviewer model should use UUID primary keys. Migrations store `subject_id` and `reviewer_id` as UUIDs.
19
+
20
+ Set the reviewer model in the initializer:
21
+
22
+ ```ruby
23
+ ActiveAdmin::Annotations.reviewer_class_name = "User"
24
+ ```
25
+
26
+ The reviewer must be available as `current_user` (or your ActiveAdmin authentication helper) when the panel renders.
27
+
28
+ The Review admin menu item is visible when `review_menu_visible` returns true for the signed-in user. Default checks `user.administrator?`. Override in the initializer:
29
+
30
+ ```ruby
31
+ ActiveAdmin::Annotations.review_menu_visible = ->(user) { user&.can_review_annotations? }
32
+ ```
33
+
34
+ ## Installation
35
+
36
+ Add to your Gemfile:
37
+
38
+ ```ruby
39
+ gem "activeadmin_annotations"
40
+ ```
41
+
42
+ Then:
43
+
44
+ ```bash
45
+ bundle install
46
+ rails activeadmin_annotations:install:migrations
47
+ rails db:migrate
48
+ ```
49
+
50
+ Configure the reviewer model and optional categories:
51
+
52
+ ```ruby
53
+ # config/initializers/activeadmin_annotations.rb
54
+ ActiveAdmin::Annotations.reviewer_class_name = "User"
55
+ ActiveAdmin::Annotations.review_menu_visible = ->(user) { user&.administrator? }
56
+ ActiveAdmin::Annotations.category_label = "Category"
57
+ ActiveAdmin::Annotations.categories = {
58
+ "needs_follow_up" => "Needs follow-up"
59
+ }
60
+ ActiveAdmin::Annotations.copy_instructions = <<~TEXT
61
+ These annotations capture span-level review notes for evaluation datasets.
62
+ TEXT
63
+ ```
64
+
65
+ `copy_instructions` can be a string or a callable `(annotation, review) -> string`.
66
+ It is prepended to the text copied from the annotation show page.
67
+
68
+ ## Host model
69
+
70
+ ```ruby
71
+ class Article < ApplicationRecord
72
+ include ActiveAdmin::Annotations::Annotatable
73
+ end
74
+ ```
75
+
76
+ ## ActiveAdmin panel (basic, no content revisions)
77
+
78
+ When the subject does not use content revisions, render the panel directly:
79
+
80
+ ```ruby
81
+ text_node helpers.activeadmin_annotations_panel(
82
+ subject: resource,
83
+ field: :body,
84
+ context: {title: resource.title, author: resource.author_name},
85
+ context_panels: [
86
+ {title: "Metadata", content: JSON.pretty_generate(resource.metadata)}
87
+ ],
88
+ content: markdown(resource.body)
89
+ )
90
+ ```
91
+
92
+ The gem stores annotations against the live subject text. If the reviewed text changes, stale detection uses `context_digest` only. No version UI is shown.
93
+
94
+ ## Content revisions (optional)
95
+
96
+ Annotations use character offsets. If reviewed text can change in place, pin annotations to a content revision.
97
+
98
+ The gem does not depend on `active_version`. Revisions are optional and disabled unless:
99
+
100
+ - `content_revision_strategy` is `:active_version` or `:auto` (default), and the subject model uses `has_revisions` from [active_version](https://github.com/amkisko/active_version.rb), or
101
+ - you provide custom revision resolvers (below).
102
+
103
+ ### Using active_version (recommended when content changes)
104
+
105
+ `active_version` is not a gem dependency. When present on a subject, the gem follows this convention:
106
+
107
+ - `subject.class.has_revisions?` is true
108
+ - `subject.current_version` returns the head revision number
109
+ - `subject.at_version(number)` returns a read-only copy at that revision
110
+
111
+ Pinned version `N` on the head `H` resolves content as:
112
+
113
+ - `N == H` → live `subject`
114
+ - `N < H` → `subject.at_version(N + 1)` (active_version stores the previous text in the next revision row)
115
+
116
+ Setup in the host app:
117
+
118
+ ```bash
119
+ rails g active_version:revisions Article
120
+ rails db:migrate
121
+ ```
122
+
123
+ ```ruby
124
+ class Article < ApplicationRecord
125
+ include ActiveAdmin::Annotations::Annotatable
126
+ has_revisions(only: %i[title body])
127
+ end
128
+ ```
129
+
130
+ Then either render the panel through `ActiveAdmin::Annotations::ReviewPanel` (handles pinning, stale state, and version filtering):
131
+
132
+ ```ruby
133
+ text_node ActiveAdmin::Annotations::ReviewPanel.render(
134
+ subject: resource,
135
+ reviewer: current_user,
136
+ field: :body,
137
+ view_context: self,
138
+ context_builder: ->(content_subject) { {title: content_subject.title} },
139
+ content_builder: ->(content_subject) { markdown(content_subject.body) },
140
+ advance_review_url: advance_annotation_review_admin_article_path(resource)
141
+ )
142
+ ```
143
+
144
+ Wire the stale-banner action on the host subject resource:
145
+
146
+ ```ruby
147
+ # app/admin/articles.rb
148
+ ActiveAdmin.register Article do
149
+ member_action :advance_annotation_review, method: :post do
150
+ context = {title: resource.title, body: resource.body}
151
+ ActiveAdmin::Annotations::ReviewService.advance_to_latest!(
152
+ subject: resource,
153
+ reviewer: current_user,
154
+ context: context,
155
+ latest_content_revision_version: resource.current_version
156
+ )
157
+ redirect_to resource_path, notice: "Review moved to the latest content version."
158
+ end
159
+ end
160
+ ```
161
+
162
+ Pass the same path helper to `ReviewPanel.render` as `advance_review_url`. The panel shows the button only when the review is stale and a URL is present.
163
+
164
+ Or pass revision options manually to `activeadmin_annotations_panel`.
165
+
166
+ Enable active_version detection explicitly:
167
+
168
+ ```ruby
169
+ ActiveAdmin::Annotations.content_revision_strategy = :active_version
170
+ ```
171
+
172
+ ### Custom revision backend (no active_version)
173
+
174
+ Disable auto-detection and provide callables (use `:auto`, not `:none`):
175
+
176
+ ```ruby
177
+ ActiveAdmin::Annotations.content_revision_strategy = :auto
178
+
179
+ ActiveAdmin::Annotations.current_content_revision_version = lambda do |subject|
180
+ subject.published_version
181
+ end
182
+
183
+ ActiveAdmin::Annotations.content_subject_for_revision = lambda do |subject, version|
184
+ subject.versions.find_by!(number: version)
185
+ end
186
+ ```
187
+
188
+ With custom resolvers, revision mode is enabled even without `active_version`.
189
+
190
+ ### Revision configuration reference
191
+
192
+ - `content_revision_strategy` — `:auto` (default), `:active_version`, or `:none` / `false`
193
+ - `current_content_revision_version` — `(subject) -> Integer` override for head version
194
+ - `content_subject_for_revision` — `(subject, version) -> Object` override for rendered content
195
+ - `content_revision_label` — `false`, format string (`"Draft %{version}"`), or `(version) -> String`
196
+ - `stale_content_review_message` — string (`%{pinned_version}`, `%{latest_version}`) or `(review, pinned_version:, latest_version:)`
197
+ - `stale_context_message` — string or `(review)` when revisions are off but `context_digest` changed
198
+
199
+ Disable all revision UI and logic:
200
+
201
+ ```ruby
202
+ ActiveAdmin::Annotations.content_revision_strategy = :none
203
+ ```
204
+
205
+ ## Storage
206
+
207
+ - `ActiveAdmin::Annotations::Review` — one row per subject + reviewer, with frozen `context_json`
208
+ - `ActiveAdmin::Annotations::Annotation` — span highlights tied to a review and field name
209
+ - `content_revision_version` — optional pin when revision mode is enabled (defaults to `0`)
210
+
211
+ ## Export
212
+
213
+ ActiveAdmin registers review and span resources. Use the Export JSONL action on the reviews index for downstream tooling.
214
+
215
+ ## Theme customization
216
+
217
+ The panel loads `activeadmin_annotations.css` and uses stable `aa-annotations-*` class names. Action buttons use ActiveAdmin's `action-item-button` so they inherit your admin chrome; colors, spacing, borders, and text highlights are overridden in the host app.
218
+
219
+ ### Override styles
220
+
221
+ Add a host stylesheet that loads after the gem asset (for example in your ActiveAdmin layout or `app/assets/stylesheets/active_admin/`):
222
+
223
+ ```css
224
+ /* app/assets/stylesheets/active_admin/custom_annotations.css */
225
+ .aa-annotations-sidebar {
226
+ border-color: var(--border-color, #e5e7eb);
227
+ }
228
+
229
+ .aa-annotations-save-button {
230
+ background-color: var(--primary, #2563eb);
231
+ border-color: var(--primary, #2563eb);
232
+ }
233
+
234
+ ::highlight(aa-annotations-saved) {
235
+ background-color: color-mix(in srgb, var(--primary, #2563eb) 30%, #fef08a);
236
+ }
237
+
238
+ ::highlight(aa-annotations-pending) {
239
+ background-color: color-mix(in srgb, var(--primary, #2563eb) 40%, #fef08a);
240
+ text-decoration-color: var(--primary, #2563eb);
241
+ }
242
+ ```
243
+
244
+ Gem defaults include `.dark .aa-annotations-*` rules for ActiveAdmin 4 dark mode. Mirror host overrides under `.dark` when you change light-mode colors.
245
+
246
+ Main layout hooks: `.aa-annotations-panel`, `.aa-annotations-layout`, `.aa-annotations-sidebar`, `.aa-annotations-annotatable`, `.aa-annotations-composer`, `.aa-annotations-stale-banner`.
247
+
248
+ ### Override partials
249
+
250
+ Copy views from the gem into `app/views/active_admin/annotations/`:
251
+
252
+ - `_panel.html.erb` — review UI and composer
253
+ - `_read_only_content.html.erb` — content-only fallback when no reviewer is signed in
254
+ - `_copy_details_action.html.erb` — copy action on the span show page
255
+
256
+ Override `_panel.html.erb` when you need different markup or to pin a custom Stimulus controller on the annotator root.
257
+
258
+ ## Authorization
259
+
260
+ The gem registers ActiveAdmin resources for reviews and spans. Member actions use your host authorization adapter.
261
+
262
+ Span create, update, destroy, and copy actions verify that the signed-in reviewer owns the target review. Host policies should still gate who may access annotation resources at all (`create?`, `update?`, `destroy?`, `show?`).
263
+
264
+ With [ActionPolicy](https://github.com/palkan/action_policy) (or ActiveAdmin's ActionPolicy adapter), allow the span Copy details action explicitly:
265
+
266
+ ```ruby
267
+ # app/policies/active_admin/annotations/annotation_policy.rb
268
+ class ActiveAdmin::Annotations::AnnotationPolicy < ApplicationPolicy
269
+ def copy_text?
270
+ show?
271
+ end
272
+ end
273
+ ```
274
+
275
+ Review resources use the usual CRUD predicates (`index?`, `show?`, `create?`, `update?`) from your app policy base class.
276
+
277
+ Place policies under `app/policies/active_admin/annotations/` so Zeitwerk maps them to `ActiveAdmin::Annotations::*Policy`.
278
+
279
+ ## Dependencies
280
+
281
+ Required:
282
+
283
+ - Rails >= 7.1
284
+ - ActiveAdmin >= 3.0
285
+
286
+ Optional (host app):
287
+
288
+ - `active_version` for revision snapshots — not bundled; follow active_version setup when content can regenerate
289
+
290
+ ## Development
291
+
292
+ From the gem root:
293
+
294
+ ```bash
295
+ bundle install
296
+ bundle exec appraisal install
297
+ bundle exec rubocop
298
+ bundle exec polyrun parallel-rspec --workers 5 --merge-failures
299
+ ```
300
+
301
+ Matrixed Rails versions use [Appraisal](https://github.com/thoughtbot/appraisal): `gemfiles/rails72.gemfile`, `rails8ruby34.gemfile`, and `rails8truffleruby.gemfile`. Run `bundle exec appraisal rspec` to execute RSpec in each gemfile context.
302
+
303
+ Shared agent guidance is managed with [pray](https://github.com/kiskolabs/pray) via `Prayfile`.
304
+
305
+ [Trunk](https://docs.trunk.io) config lives in `.trunk/`; CI can run `trunk` via `.github/workflows/_trunk_check.yml`. Releases: `make release` or `usr/bin/release.rb`.
306
+
307
+ ## Contributing
308
+
309
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and pull requests are welcome at https://github.com/amkisko/activeadmin_annotations.rb/issues
310
+
311
+ ## License
312
+
313
+ MIT — see [LICENSE.md](LICENSE.md).
314
+
315
+ ## Sponsors
316
+
317
+ Sponsored by [Kisko Labs](https://www.kiskolabs.com).
318
+
319
+ <a href="https://www.kiskolabs.com">
320
+ <img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
321
+ </a>
data/SECURITY.md ADDED
@@ -0,0 +1,27 @@
1
+ # SECURITY
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Do NOT open a public GitHub issue for security vulnerabilities.
6
+
7
+ Email security details to: security@kiskolabs.com
8
+
9
+ Include: description, steps to reproduce, potential impact, and suggested fix (if available).
10
+
11
+ ### Response timeline
12
+
13
+ - We will acknowledge receipt of your report
14
+ - We will provide an initial assessment
15
+ - We will keep you informed of our progress and resolution timeline
16
+
17
+ ### Disclosure policy
18
+
19
+ - We will work with you to understand and resolve the issue
20
+ - We will credit you for the discovery (unless you prefer to remain anonymous)
21
+ - We will publish a security advisory after the vulnerability is patched
22
+ - We will coordinate public disclosure with you
23
+
24
+ ## Automation security
25
+
26
+ - Context isolation: it is strictly forbidden to include production credentials, API keys, or personally identifiable information in prompts sent to third-party LLMs or automation services.
27
+ - Supply chain: all automated dependencies must be verified.
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/activeadmin/annotations/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "activeadmin_annotations"
7
+ spec.version = ActiveAdmin::Annotations::VERSION
8
+ spec.authors = ["Andrei Makarov"]
9
+ spec.email = ["contact@kiskolabs.com"]
10
+
11
+ spec.summary = "Span-level annotations for ActiveAdmin"
12
+ spec.description = "Collect text highlights and comments inside a bounded ActiveAdmin block, with optional review context, content revision pinning, and JSONL export."
13
+ spec.license = "MIT"
14
+ spec.platform = Gem::Platform::RUBY
15
+ spec.required_ruby_version = ">= 3.2.0"
16
+
17
+ repository_url = "https://github.com/amkisko/activeadmin_annotations.rb"
18
+
19
+ spec.homepage = repository_url
20
+ spec.metadata = {
21
+ "homepage_uri" => repository_url,
22
+ "source_code_uri" => "#{repository_url}/tree/main",
23
+ "changelog_uri" => "#{repository_url}/blob/main/CHANGELOG.md",
24
+ "bug_tracker_uri" => "#{repository_url}/issues",
25
+ "documentation_uri" => "#{repository_url}#readme",
26
+ "rubygems_mfa_required" => "true"
27
+ }
28
+
29
+ spec.files = Dir.chdir(__dir__) do
30
+ %w[
31
+ CHANGELOG.md
32
+ CODE_OF_CONDUCT.md
33
+ CONTRIBUTING.md
34
+ GOVERNANCE.md
35
+ LICENSE.md
36
+ README.md
37
+ SECURITY.md
38
+ activeadmin_annotations.gemspec
39
+ ].select { |path| File.file?(path) } +
40
+ Dir["{app,admin,config,db,lib,sig}/**/*"]
41
+ end
42
+
43
+ spec.require_paths = ["lib"]
44
+
45
+ spec.add_dependency "rails", ">= 7.1"
46
+ spec.add_dependency "activeadmin", ">= 3.0"
47
+
48
+ spec.add_development_dependency "appraisal", "~> 2"
49
+ spec.add_development_dependency "bundler-audit", "~> 0.9"
50
+ spec.add_development_dependency "bundler", ">= 2"
51
+ spec.add_development_dependency "polyrun", ">= 2.2.0"
52
+ spec.add_development_dependency "prosopite", "~> 2.0"
53
+ spec.add_development_dependency "rake", "~> 13"
54
+ spec.add_development_dependency "propshaft", ">= 1.0"
55
+ spec.add_development_dependency "factory_bot", "~> 6"
56
+ spec.add_development_dependency "rspec", "~> 3"
57
+ spec.add_development_dependency "rspec-rails", ">= 6"
58
+ spec.add_development_dependency "sqlite3", ">= 1"
59
+ spec.add_development_dependency "rubocop-rails", "~> 2.34"
60
+ spec.add_development_dependency "rubocop-rspec", "~> 3.8"
61
+ spec.add_development_dependency "rubocop-thread_safety", "~> 0.7"
62
+ spec.add_development_dependency "standard", "~> 1.52"
63
+ spec.add_development_dependency "standard-custom", "~> 1.0"
64
+ spec.add_development_dependency "standard-performance", "~> 1.8"
65
+ spec.add_development_dependency "standard-rails", "~> 1.5"
66
+ spec.add_development_dependency "standard-rspec", "~> 0.3"
67
+ spec.add_development_dependency "rbs", "~> 3"
68
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveAdmin.register ActiveAdmin::Annotations::Review, as: "annotation_reviews" do
4
+ menu priority: 3,
5
+ label: "Review",
6
+ if: proc { ActiveAdmin::Annotations.review_menu_visible.call(current_user) },
7
+ menu_name: :auxiliary
8
+
9
+ controller do
10
+ def scoped_collection
11
+ super.includes(:annotations)
12
+ end
13
+ end
14
+
15
+ actions :index, :show, :create, :update
16
+
17
+ permit_params :review_status, :notes, :metadata_json
18
+
19
+ filter :subject_type, as: :select
20
+ filter :subject_id
21
+ filter :reviewer_id, label: "Reviewer"
22
+ filter :review_status, as: :select, collection: ActiveAdmin::Annotations::Review::REVIEW_STATUSES
23
+ filter :context_digest
24
+ filter :created_at
25
+
26
+ scope :all, default: true
27
+ scope :pending
28
+ scope :reviewed
29
+ scope :escalated
30
+ scope :needs_follow_up
31
+
32
+ index do
33
+ selectable_column
34
+ id_column
35
+ column :subject_type
36
+ column :subject_id
37
+ column :reviewer
38
+ column :review_status
39
+ column("Annotations") { |review| review.annotations.size }
40
+ column :context_digest
41
+ column :created_at
42
+ actions
43
+ end
44
+
45
+ show do
46
+ attributes_table_for(resource) do
47
+ row :id
48
+ row :subject do |review|
49
+ label = "#{review.subject_type} ##{review.subject_id}"
50
+ subject = review.subject
51
+ if subject.present?
52
+ auto_link(subject, label)
53
+ else
54
+ label
55
+ end
56
+ end
57
+ row :reviewer
58
+ row :review_status
59
+ row :notes
60
+ row :context_digest
61
+ if ActiveAdmin::Annotations::ContentRevision.enabled_for?(resource.subject)
62
+ row :content_revision_version
63
+ end
64
+ row("Context stale?") { |review| review.context_stale? ? "yes" : "no" }
65
+ row :created_at
66
+ row :updated_at
67
+ end
68
+
69
+ panel "Context" do
70
+ pre JSON.pretty_generate(resource.context_json)
71
+ end
72
+
73
+ panel "Annotations" do
74
+ if resource.annotations.any?
75
+ table_for(resource.annotations.order(:created_at)) do
76
+ column :field_name
77
+ column :selected_text
78
+ column :comment
79
+ column :category
80
+ if ActiveAdmin::Annotations::ContentRevision.enabled_for?(resource.subject)
81
+ column :content_revision_version
82
+ end
83
+ column :start_offset
84
+ column :end_offset
85
+ end
86
+ else
87
+ para "No annotations yet."
88
+ end
89
+ end
90
+ end
91
+
92
+ collection_action :export_jsonl, method: :get do
93
+ exporter = ActiveAdmin::Annotations::Exporter.new(reviews: collection)
94
+ send_data exporter.to_jsonl,
95
+ filename: "annotation-reviews-#{Time.current.strftime("%Y%m%d%H%M%S")}.jsonl",
96
+ type: "application/jsonl"
97
+ end
98
+
99
+ action_item :export_jsonl, only: :index do
100
+ link_to "Export JSONL", export_jsonl_admin_annotation_reviews_path(request.query_parameters), class: "action-item-button"
101
+ end
102
+ end