fosm-rails 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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +384 -0
  3. data/LICENSE +115 -0
  4. data/README.md +322 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/stylesheets/fosm/rails/application.css +15 -0
  7. data/app/controllers/fosm/admin/agents_controller.rb +242 -0
  8. data/app/controllers/fosm/admin/apps_controller.rb +34 -0
  9. data/app/controllers/fosm/admin/base_controller.rb +15 -0
  10. data/app/controllers/fosm/admin/dashboard_controller.rb +25 -0
  11. data/app/controllers/fosm/admin/settings_controller.rb +44 -0
  12. data/app/controllers/fosm/admin/transitions_controller.rb +22 -0
  13. data/app/controllers/fosm/admin/webhooks_controller.rb +37 -0
  14. data/app/controllers/fosm/application_controller.rb +23 -0
  15. data/app/controllers/fosm/rails/application_controller.rb +6 -0
  16. data/app/helpers/fosm/application_helper.rb +19 -0
  17. data/app/helpers/fosm/rails/application_helper.rb +11 -0
  18. data/app/jobs/fosm/application_job.rb +6 -0
  19. data/app/jobs/fosm/rails/application_job.rb +6 -0
  20. data/app/jobs/fosm/webhook_delivery_job.rb +52 -0
  21. data/app/models/fosm/application_record.rb +6 -0
  22. data/app/models/fosm/rails/application_record.rb +7 -0
  23. data/app/models/fosm/transition_log.rb +27 -0
  24. data/app/models/fosm/webhook_subscription.rb +15 -0
  25. data/app/views/fosm/admin/agents/chat.html.erb +242 -0
  26. data/app/views/fosm/admin/agents/show.html.erb +166 -0
  27. data/app/views/fosm/admin/apps/show.html.erb +114 -0
  28. data/app/views/fosm/admin/dashboard/index.html.erb +82 -0
  29. data/app/views/fosm/admin/settings/show.html.erb +63 -0
  30. data/app/views/fosm/admin/transitions/index.html.erb +65 -0
  31. data/app/views/fosm/admin/webhooks/index.html.erb +51 -0
  32. data/app/views/fosm/admin/webhooks/new.html.erb +45 -0
  33. data/app/views/layouts/fosm/application.html.erb +41 -0
  34. data/app/views/layouts/fosm/rails/application.html.erb +17 -0
  35. data/config/routes.rb +17 -0
  36. data/db/migrate/20240101000001_create_fosm_transition_logs.rb +23 -0
  37. data/db/migrate/20240101000002_create_fosm_webhook_subscriptions.rb +16 -0
  38. data/lib/fosm/agent.rb +232 -0
  39. data/lib/fosm/configuration.rb +50 -0
  40. data/lib/fosm/engine.rb +133 -0
  41. data/lib/fosm/errors.rb +31 -0
  42. data/lib/fosm/lifecycle/definition.rb +103 -0
  43. data/lib/fosm/lifecycle/event_definition.rb +27 -0
  44. data/lib/fosm/lifecycle/guard_definition.rb +16 -0
  45. data/lib/fosm/lifecycle/side_effect_definition.rb +16 -0
  46. data/lib/fosm/lifecycle/state_definition.rb +18 -0
  47. data/lib/fosm/lifecycle.rb +173 -0
  48. data/lib/fosm/rails/engine.rb +9 -0
  49. data/lib/fosm/rails/version.rb +9 -0
  50. data/lib/fosm/rails.rb +9 -0
  51. data/lib/fosm/registry.rb +29 -0
  52. data/lib/fosm/version.rb +3 -0
  53. data/lib/fosm-rails.rb +40 -0
  54. data/lib/generators/fosm/app/app_generator.rb +106 -0
  55. data/lib/generators/fosm/app/templates/agent.rb.tt +26 -0
  56. data/lib/generators/fosm/app/templates/controller.rb.tt +56 -0
  57. data/lib/generators/fosm/app/templates/migration.rb.tt +14 -0
  58. data/lib/generators/fosm/app/templates/model.rb.tt +31 -0
  59. data/lib/generators/fosm/app/templates/views/_form.html.erb.tt +24 -0
  60. data/lib/generators/fosm/app/templates/views/index.html.erb.tt +37 -0
  61. data/lib/generators/fosm/app/templates/views/new.html.erb.tt +4 -0
  62. data/lib/generators/fosm/app/templates/views/show.html.erb.tt +57 -0
  63. data/lib/tasks/fosm/rails_tasks.rake +4 -0
  64. metadata +139 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: caa495883333e121bf051fcc48f83f16a9182bf0b3ab04257f0c6eb4d6aa107b
4
+ data.tar.gz: 95d917636381c725c1bc35f105775bf7dd262064143d1779bf2eee24a092fb6a
5
+ SHA512:
6
+ metadata.gz: b0a8dcc832dc6a0b459023dc630a224d45d321add78a180d96d0e588b7080e1546809001bbd8e48441e67ca39062fca5ad3fb14e2319d2001e3ad20b395217ba
7
+ data.tar.gz: 73ae82e375097698267f092dbffea06f2472ef36ec4f158213daf3186890d51ec5a53e2f7fb26fa974827f0180bae3bf6bf2e9c62100e7b74828f2bf1f9dafa9
data/AGENTS.md ADDED
@@ -0,0 +1,384 @@
1
+ # AGENTS.md — Understanding and Extending fosm-rails
2
+
3
+ This file is for AI coding agents, contributors, and developers who want to understand the deep design philosophy behind this engine and contribute to it thoughtfully.
4
+
5
+ ---
6
+
7
+ ## What is FOSM?
8
+
9
+ **FOSM** stands for **Finite Object State Machine**.
10
+
11
+ The "finite" part is standard: a countable number of states, with explicit transitions between them. The "object" part is what makes it different: the state machine is **bound to a specific business entity** — an Invoice, a Candidate, a Contract, a Deal. The lifecycle is not an abstract workflow. It *is* the domain object.
12
+
13
+ ### The core problem FOSM solves
14
+
15
+ Every business application has objects that move through lifecycles. An Invoice gets drafted, sent, paid or disputed, possibly cancelled. A Candidate gets applied, screened, interviewed, offered, hired or rejected. A Contract gets drafted, reviewed, signed, executed.
16
+
17
+ The conventional CRUD approach models these as a table row with a `status` column — a mutable string with no opinions about what comes before or after. The business rules that govern valid transitions end up scattered across the codebase:
18
+
19
+ - `if`-statements in controllers
20
+ - `before_save` callbacks in models
21
+ - validation logic in service objects
22
+ - guard conditions in Sidekiq jobs
23
+
24
+ None of it is connected. Ask a new developer "what are the rules for invoices?" and the honest answer is: read every file that touches the `Invoice` model, then pray.
25
+
26
+ FOSM declares the rules **as part of what it means to be an Invoice**:
27
+
28
+ ```ruby
29
+ class Invoice < ApplicationRecord
30
+ include Fosm::Lifecycle
31
+
32
+ lifecycle do
33
+ state :draft, initial: true
34
+ state :sent
35
+ state :paid, terminal: true
36
+ state :overdue
37
+ state :cancelled, terminal: true
38
+
39
+ event :send_invoice, from: :draft, to: :sent
40
+ event :pay, from: [:sent, :overdue], to: :paid
41
+ event :mark_overdue, from: :sent, to: :overdue
42
+ event :cancel, from: [:draft, :sent], to: :cancelled
43
+
44
+ guard :has_line_items, on: :send_invoice do |inv|
45
+ inv.line_items.any?
46
+ end
47
+
48
+ side_effect :notify_client, on: :send_invoice do |inv, transition|
49
+ InvoiceMailer.send_to_client(inv).deliver_later
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ That block is the complete lifecycle specification. There is no path from `draft` to `paid` — the machine won't allow it. There is no path from `paid` to anything — it's terminal. You can see the five states, the four events, the guard condition, and the side effect in twenty lines of code.
56
+
57
+ ---
58
+
59
+ ## The abstraction: one lifecycle definition, three superpowers
60
+
61
+ When a developer writes a lifecycle block and runs the generator, they get three things without writing any additional code:
62
+
63
+ **1. A CRUD application that enforces the lifecycle**
64
+ The generated controller and views allow users to create records and fire transitions. The UI only offers valid actions — the machine decides what buttons appear. Guards, terminal states, and invalid transitions are enforced at the Rails level.
65
+
66
+ **2. An immutable audit trail**
67
+ Every state change is written to `fosm_transition_logs` with the actor, timestamp, and metadata. No configuration required. The log is tamper-proof — read-only at the database level.
68
+
69
+ **3. A fully-configured AI agent**
70
+ The `Fosm::Agent` base class reads the lifecycle definition at runtime and auto-generates a complete set of Gemlings tools. You don't write a single line of agent code to get a working agent. The agent appears immediately at `/fosm/admin/apps/:slug/agent` after the lifecycle is defined.
71
+
72
+ This is the beauty of the FOSM abstraction: **the lifecycle is the single source of truth** for the CRUD rules, the audit log schema, and the AI agent's capabilities. They cannot drift from each other because they all read from the same definition.
73
+
74
+ ---
75
+
76
+ ## Why FOSM matters now
77
+
78
+ State machines have existed for sixty years. The reason they didn't become the default paradigm for business software is the **specification problem**: you had to enumerate every state, every transition, every guard condition upfront. Business processes are messy, requirements shift, and Agile won because upfront specification is too expensive in a fast-moving world.
79
+
80
+ **AI dissolves this problem.**
81
+
82
+ An LLM already knows what an invoice lifecycle looks like. Tell it "build me an invoicing module" and it produces a reasonable first-draft state machine in seconds — because invoice processing is one of the most documented business processes in human history.
83
+
84
+ But there is a deeper point. When you pair AI with a FOSM-structured codebase, you get **bounded autonomy**:
85
+
86
+ - An AI agent operating within a FOSM system can only do what the state machine allows
87
+ - It cannot skip a step, invent a transition, or operate outside the declared lifecycle
88
+ - The machine is the guardrail — you don't need to trust the AI's judgment, only the state machine
89
+ - When the AI fires `send_invoice!(actor: :agent)`, if the invoice isn't in `draft` state, the machine refuses
90
+
91
+ **FOSM makes AI safe. AI makes FOSM practical. Neither works as well without the other.**
92
+
93
+ ---
94
+
95
+ ## Design principles of this engine
96
+
97
+ When contributing to fosm-rails, keep these principles in mind. They are not conventions — they are load-bearing.
98
+
99
+ ### 1. `fire!` is the only mutation path
100
+
101
+ State must never change via `update(state: "paid")` or direct attribute assignment. The only valid path is `fire!(:event_name, actor:)`. This is what makes the audit trail complete, the guards enforceable, and the AI agents bounded.
102
+
103
+ **Do not** add any method to `Fosm::Lifecycle` that changes state without going through `fire!`.
104
+
105
+ ### 2. Guards are pure functions
106
+
107
+ A guard receives the record and returns true or false. It has no side effects. This is critical for the `can_fire?` method — it is called to check availability without triggering anything. If a guard had side effects, `can_fire?` would have side effects, which would break the admin UI, the agent's `available_events_for_*` tool, and any code that inspects state.
108
+
109
+ **Do not** allow guards to modify state or trigger external calls.
110
+
111
+ ### 3. Every transition is logged
112
+
113
+ `Fosm::TransitionLog` is immutable and append-only. The `before_update` and `before_destroy` callbacks raise `ActiveRecord::ReadOnlyRecord`. This is intentional — the audit trail must be complete and tamper-proof.
114
+
115
+ **Do not** add `updated_at` to `fosm_transition_logs`. Do not add a soft-delete mechanism.
116
+
117
+ ### 4. Terminal states are irreversible by design
118
+
119
+ When a state is declared `terminal: true`, any attempt to fire an event from it raises `Fosm::TerminalState`. This is the architectural equivalent of a physical lock. Business logic that needs to "undo" a terminal state should use a compensating event (e.g., `reopen` that goes from `cancelled` to `draft`), not by removing the terminal constraint.
120
+
121
+ ### 5. The lifecycle definition is the documentation
122
+
123
+ The admin explorer renders the lifecycle definition directly from the Ruby code — it doesn't read a separate diagram file or database record. This means the documentation is always accurate because it IS the running code.
124
+
125
+ **Do not** add a separate "description" mechanism that could drift from the actual implementation.
126
+
127
+ ### 6. AI agents are bounded, not trusted
128
+
129
+ The `Fosm::Agent` base class generates exactly one Gemlings tool per lifecycle event. The tool calls `fire!` which enforces the machine rules. The AI cannot fire an event that doesn't exist. The AI cannot bypass a guard. The AI cannot modify state directly.
130
+
131
+ When adding new agent capabilities, add new **lifecycle events** (which automatically generate new agent tools), not new raw database tools.
132
+
133
+ ---
134
+
135
+ ## Engine architecture
136
+
137
+ ```
138
+ lib/
139
+ fosm/
140
+ lifecycle.rb ← ActiveSupport::Concern — the main DSL mixin
141
+ lifecycle/
142
+ definition.rb ← Holds states/events/guards/side_effects for one model
143
+ state_definition.rb ← Value object: name, initial?, terminal?
144
+ event_definition.rb ← Value object: name, from_states, to_state, guards, side_effects
145
+ guard_definition.rb ← Named callable: (record) → bool
146
+ side_effect_definition.rb ← Named callable: (record, transition) → void
147
+ agent.rb ← Base class: model_class DSL + Gemlings tool generation
148
+ configuration.rb ← Fosm.configure { } block
149
+ registry.rb ← Global slug → model_class map
150
+ errors.rb ← Fosm::InvalidTransition, GuardFailed, etc.
151
+ engine.rb ← Rails::Engine, migration hooks, auto-registration
152
+ fosm-rails.rb ← Entry point
153
+
154
+ app/
155
+ models/fosm/
156
+ transition_log.rb ← Immutable audit trail (shared across all FOSM apps)
157
+ webhook_subscription.rb ← Admin-configured HTTP callbacks
158
+ controllers/fosm/
159
+ application_controller.rb ← Inherits from configured base_controller
160
+ admin/
161
+ base_controller.rb ← Admin auth before_action
162
+ dashboard_controller.rb
163
+ apps_controller.rb
164
+ transitions_controller.rb
165
+ webhooks_controller.rb
166
+ jobs/fosm/
167
+ webhook_delivery_job.rb ← Async HTTP POST with HMAC signing, retries
168
+
169
+ lib/generators/fosm/app/
170
+ app_generator.rb ← rails generate fosm:app
171
+ templates/
172
+ model.rb.tt
173
+ controller.rb.tt
174
+ agent.rb.tt
175
+ migration.rb.tt
176
+ routes.rb.tt
177
+ views/
178
+ ```
179
+
180
+ ---
181
+
182
+ ## How `fire!` works
183
+
184
+ ```
185
+ record.fire!(:send_invoice, actor: current_user)
186
+
187
+ 1. Look up the event definition in fosm_lifecycle
188
+ 2. Check: does the event exist? → raise UnknownEvent if not
189
+ 3. Check: is current state terminal? → raise TerminalState if yes
190
+ 4. Check: is the event valid from current state? → raise InvalidTransition if not
191
+ 5. Run guards: each guard.call(record) → raise GuardFailed if any return false
192
+ 6. Begin database transaction:
193
+ a. UPDATE record SET state = 'sent'
194
+ b. INSERT INTO fosm_transition_logs (...)
195
+ c. Run each side_effect.call(record, transition_data)
196
+ d. COMMIT (or ROLLBACK if any step raises)
197
+ 7. Enqueue WebhookDeliveryJob asynchronously (outside transaction)
198
+ 8. Return true
199
+ ```
200
+
201
+ The transaction ensures that if a side effect raises, the state update is rolled back. The webhook job fires outside the transaction so it doesn't delay the response and doesn't roll back if the HTTP call fails.
202
+
203
+ ---
204
+
205
+ ## Gemlings: the required agent dependency
206
+
207
+ `gemlings` is declared as a **required dependency** in `fosm-rails.gemspec` — not optional. This is a deliberate design decision: the agent capability is not a plugin or an afterthought, it is a first-class output of every lifecycle definition.
208
+
209
+ When you add `gem "fosm-rails"` to a project, you get the agent framework automatically. Set `ANTHROPIC_API_KEY` (or another provider key such as `OPENAI_API_KEY` or `GEMINI_API_KEY`) in your environment and the agent is ready to use.
210
+
211
+ Supported LLM providers come from the `ruby_llm` gem that Gemlings depends on. The default model is `anthropic/claude-sonnet-4-20250514`. Override it per-agent:
212
+
213
+ ```ruby
214
+ class Fosm::InvoiceAgent < Fosm::Agent
215
+ model_class Fosm::Invoice
216
+ default_model "openai/gpt-4o"
217
+ end
218
+ ```
219
+
220
+ ### Compatibility note
221
+
222
+ The engine's `initializer "fosm.configuration"` includes two runtime patches to `Gemlings::Memory` and `Gemlings::Models::RubyLLMAdapter`. These patches fix Anthropic API incompatibilities in Gemlings' `ToolCallingAgent`:
223
+
224
+ 1. **Trailing whitespace** — Anthropic rejects assistant messages whose content ends with whitespace. The patch strips it from all messages in `to_messages`.
225
+ 2. **Tool result format** — After a tool_use block, Anthropic requires a structured `tool_result` block in the next user message. Gemlings generates a plain `"Observation: ..."` text message instead. The patch rewrites these into the correct `{ type: "tool_result", tool_use_id: ..., content: ... }` format, and `load_messages` uses `role: :tool` when passing them to `ruby_llm`.
226
+
227
+ These patches are applied once at boot via `prepend` and are invisible to application code. If Gemlings fixes these issues upstream, the patches become no-ops.
228
+
229
+ ---
230
+
231
+ ## The Admin Agent Explorer
232
+
233
+ For every registered FOSM app, the admin provides two agent-specific pages:
234
+
235
+ **`/fosm/admin/apps/:slug/agent`** — The Tool Catalog
236
+ - Lists all auto-generated tools (read tools and mutate tools) with their descriptions and parameter signatures
237
+ - Provides a **Direct Tool Tester** — invoke any tool from the browser with no LLM involved. Useful for verifying tool behaviour and debugging lifecycle configurations.
238
+ - Shows the **System Prompt** — the exact constraints injected into the LLM, including terminal states and the instruction to always call `available_events_for_*` before firing.
239
+
240
+ **`/fosm/admin/apps/:slug/agent/chat`** — The Agent Chat
241
+ - Multi-turn conversation with the live Gemlings agent
242
+ - Each response shows a collapsible **reasoning trace**: tool calls, observations, and the LLM's thought process
243
+ - "New conversation" clears context and starts fresh
244
+
245
+ The Tool Tester is particularly valuable during development: you can verify that your guards are working correctly, that events are available in the right states, and that your lifecycle behaves as designed — all without writing a test.
246
+
247
+ ---
248
+
249
+ ## The Gemlings agent tools
250
+
251
+ For a model with this lifecycle:
252
+
253
+ ```ruby
254
+ lifecycle do
255
+ state :draft, initial: true
256
+ state :sent
257
+ state :paid, terminal: true
258
+
259
+ event :send_invoice, from: :draft, to: :sent
260
+ event :pay, from: :sent, to: :paid
261
+ end
262
+ ```
263
+
264
+ The following Gemlings tools are auto-generated:
265
+
266
+ | Tool name | What it does |
267
+ |---|---|
268
+ | `list_invoices` | `Invoice.all` (or filtered by `state:`) |
269
+ | `get_invoice` | `Invoice.find(id)` with state + available_events |
270
+ | `available_events_for_invoice` | `record.available_events` |
271
+ | `transition_history_for_invoice` | `TransitionLog.for_record(...)` |
272
+ | `send_invoice_invoice` | `record.fire!(:send_invoice, actor: :agent)` |
273
+ | `pay_invoice` | `record.fire!(:pay, actor: :agent)` |
274
+
275
+ The event tools are named `{event_name}_{model_name}` to avoid ambiguity in multi-model agent workflows.
276
+
277
+ The agent's system prompt includes:
278
+ - The valid states for the model
279
+ - The terminal states
280
+ - The available events
281
+ - Explicit instructions to always call `available_events_for_*` before firing
282
+ - Instructions to accept `{ success: false }` responses without retrying
283
+
284
+ ---
285
+
286
+ ## How to add a new FOSM app
287
+
288
+ ```bash
289
+ rails generate fosm:app lead_capture \
290
+ --fields first_name:string last_name:string email:string company:string \
291
+ --states new,qualified,contacted,converted,lost \
292
+ --access authenticate_user!
293
+ ```
294
+
295
+ Then:
296
+ 1. Edit `app/models/fosm/lead_capture.rb` — fill in the events
297
+ 2. Edit `app/agents/fosm/lead_capture_agent.rb` — add custom tools if needed
298
+ 3. Edit `app/views/fosm/lead_capture/` — customize the UI
299
+ 4. `rails db:migrate`
300
+ 5. Visit `/fosm/apps/lead_captures`
301
+
302
+ ---
303
+
304
+ ## How to extend the admin UI
305
+
306
+ The admin views are plain ERB in `app/views/fosm/admin/`. They use basic HTML with Tailwind-compatible classes. To customize for a specific host app (e.g., to use the app's UI component library), override the views by creating matching paths in the host app:
307
+
308
+ ```
309
+ app/views/fosm/admin/dashboard/index.html.erb ← overrides the engine view
310
+ ```
311
+
312
+ Rails view inheritance means host app views take precedence over engine views.
313
+
314
+ ---
315
+
316
+ ## Testing FOSM models
317
+
318
+ ```ruby
319
+ # test/models/fosm/invoice_test.rb
320
+ class Fosm::InvoiceTest < ActiveSupport::TestCase
321
+ setup do
322
+ @invoice = Fosm::Invoice.create!(name: "Test", amount: 100, state: "draft")
323
+ end
324
+
325
+ test "draft invoice can be sent" do
326
+ assert @invoice.can_send_invoice?
327
+ @invoice.send_invoice!(actor: :test)
328
+ assert @invoice.sent?
329
+ end
330
+
331
+ test "cannot pay a draft invoice directly" do
332
+ assert_raises(Fosm::InvalidTransition) do
333
+ @invoice.pay!(actor: :test)
334
+ end
335
+ end
336
+
337
+ test "paid invoice is terminal" do
338
+ @invoice.send_invoice!(actor: :test)
339
+ @invoice.pay!(actor: :test)
340
+ assert_raises(Fosm::TerminalState) do
341
+ @invoice.cancel!(actor: :test)
342
+ end
343
+ end
344
+
345
+ test "every transition is logged" do
346
+ @invoice.send_invoice!(actor: :test)
347
+ log = Fosm::TransitionLog.for_record("Fosm::Invoice", @invoice.id).last
348
+ assert_equal "send_invoice", log.event_name
349
+ assert_equal "draft", log.from_state
350
+ assert_equal "sent", log.to_state
351
+ end
352
+
353
+ test "guard blocks sending empty invoice" do
354
+ empty = Fosm::Invoice.create!(name: "Empty", amount: 0, state: "draft")
355
+ assert_raises(Fosm::GuardFailed) do
356
+ empty.send_invoice!(actor: :test)
357
+ end
358
+ end
359
+ end
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Contributing guidelines
365
+
366
+ 1. **Read the design principles above** before writing any code
367
+ 2. **No direct state mutations** — always go through `fire!`
368
+ 3. **Keep the lifecycle DSL simple** — resist adding complexity (priorities, concurrent states, history states). FOSM is deliberately simple. If you need those features, look at XState or Statecharts.
369
+ 4. **The admin UI is secondary** — the DSL and the transition log are the core. Admin views can be overridden by host apps.
370
+ 5. **Test the lifecycle, not the persistence** — unit tests for FOSM models should test state machine behavior, not database queries
371
+ 6. **Document new events in lifecycles** — use `guard` and `side_effect` names that are self-documenting
372
+
373
+ ---
374
+
375
+ ## Key references
376
+
377
+ - **FOSM paper**: [parolkar.com/fosm](https://parolkar.com/fosm)
378
+ - **FOSM book **: [fosm-book.inloop.studio](https://fosm-book.inloop.studio)
379
+ - **Gemlings** (AI agent framework): [github.com/khasinski/gemlings](https://github.com/khasinski/gemlings)
380
+ - **Rails Engine Guide**: [guides.rubyonrails.org/engines.html](https://guides.rubyonrails.org/engines.html)
381
+
382
+ ---
383
+
384
+ *fosm-rails is an open-source implementation of ideas from Abhishek Parolkar's FOSM research. The goal is to make business software that is auditable, AI-safe, and self-documenting by design.*
data/LICENSE ADDED
@@ -0,0 +1,115 @@
1
+ # Functional Source License, Version 1.1, Apache 2.0 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Abhishek Parolkar and INLOOP.STUDIO PTE LTD
10
+
11
+ ## Change Date
12
+
13
+ Three years from the release date of each version of the Software and Content.
14
+
15
+ ## Terms and Conditions
16
+
17
+ ### Licensor ("We")
18
+
19
+ Abhishek Parolkar, INLOOP.STUDIO PTE LTD, and their affiliated entities, the party offering the Software and Content under these Terms and Conditions.
20
+
21
+ ### The Software and Content
22
+
23
+ The "Software and Content" refers to each version of the software, documentation, approaches, strategies, methodologies, and all other materials that we make available under these Terms and Conditions, as indicated by our inclusion of these Terms and Conditions with the Software and Content.
24
+
25
+ ### License Grant
26
+
27
+ Subject to your compliance with this License Grant and the Patents,
28
+ Redistribution and Trademark clauses below, we hereby grant you the right to
29
+ use, copy, modify, create derivative works, publicly perform, publicly display
30
+ and redistribute the Software and Content for any Permitted Purpose identified below.
31
+
32
+ ### Permitted Purpose
33
+
34
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
35
+ means making the Software and Content available to others in a commercial product or
36
+ service that:
37
+
38
+ 1. substitutes for the Software and Content;
39
+
40
+ 2. substitutes for any other product or service we offer using the Software and Content
41
+ that exists as of the date we make the Software and Content available; or
42
+
43
+ 3. offers the same or substantially similar functionality as the Software and Content,
44
+ including making the Software and Content available as a hosted or managed service
45
+ (e.g., software-as-a-service, platform-as-a-service, or any other on-demand service)
46
+ where third parties access the functionality of the Software and Content.
47
+
48
+ Permitted Purposes specifically include using the Software and Content:
49
+
50
+ 1. for your internal use and access;
51
+
52
+ 2. for non-commercial education;
53
+
54
+ 3. for non-commercial research; and
55
+
56
+ 4. in connection with professional services that you provide to a licensee
57
+ using the Software and Content in accordance with these Terms and Conditions.
58
+
59
+ ### Patents
60
+
61
+ To the extent your use for a Permitted Purpose would necessarily infringe our
62
+ patents, the license grant above includes a license under our patents. If you
63
+ make a claim against any party that the Software and Content infringes or contributes to
64
+ the infringement of any patent, then your patent license to the Software and Content ends
65
+ immediately.
66
+
67
+ ### Redistribution
68
+
69
+ The Terms and Conditions apply to all copies, modifications and derivatives of
70
+ the Software and Content.
71
+
72
+ If you redistribute any copies, modifications or derivatives of the Software and Content,
73
+ you must include a copy of or a link to these Terms and Conditions and not
74
+ remove any copyright notices provided in or with the Software and Content.
75
+
76
+ ### Disclaimer
77
+
78
+ THE SOFTWARE AND CONTENT IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
79
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
80
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
81
+
82
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
83
+ SOFTWARE AND CONTENT, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
84
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
85
+
86
+ ### Trademarks and Domain Names
87
+
88
+ Except for displaying the License Details and identifying us as the origin of
89
+ the Software and Content, you have no right under these Terms and Conditions to use our
90
+ trademarks, trade names, service marks or product names.
91
+ The name "FOSM-RAILS" is exclusive
92
+ property of Abhishek Parolkar with unlimited license to INLOOP.STUDIO PTE LTD. No right or license is granted
93
+ to use these names or domains in any manner whatsoever without the express written
94
+ permission of Abhishek Parolkar. Any unauthorized use of these names or domains is
95
+ strictly prohibited.
96
+
97
+ ## Grant of Future License
98
+
99
+ We hereby irrevocably grant you an additional license to use the Software and Content under
100
+ the Apache License, Version 2.0 that is effective on the second anniversary of
101
+ the date we make the Software and Content available. On or after that date, you may use the
102
+ Software and Content under the Apache License, Version 2.0, in which case the following
103
+ will apply:
104
+
105
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
106
+ this file except in compliance with the License.
107
+
108
+ You may obtain a copy of the License at
109
+
110
+ [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
111
+
112
+ Unless required by applicable law or agreed to in writing, software distributed
113
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
114
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
115
+ specific language governing permissions and limitations under the License.