root/doc/todo.txt

Revision 203:0329dd32082b, 58.0 kB (checked in by "Brian Warner <warner@lothar.com>", 1 year ago)

docs: rename some files

Line 
1 -*- outline -*-
2
3 non-independent things left to do on newpb. These require deeper magic or
4 can not otherwise be done casually. Many of these involve fundamental
5 protocol issues, and therefore need to be decided sooner rather than later.
6
7 * summary
8 ** protocol issues
9 *** negotiation
10 *** VOCABADD/DEL/SET sequences
11 *** remove 'copy' prefix from RemoteCopy type sequences?
12 *** smaller scope for OPEN-counter reference numbers?
13 ** implementation issues
14 *** cred
15 *** oldbanana compatibility
16 *** Copyable/RemoteCopy default to __getstate__ or self.__dict__ ?
17 *** RIFoo['bar'] vs RIFoo.bar (should RemoteInterface inherit from Interface?)
18 *** constrain ReferenceUnslicer
19 *** serialize target.remote_foo usefully
20
21 * decide whether to accept positional args in non-constrained methods
22
23 DEFERRED until after 2.0
24 <glyph> warner: that would be awesome but let's do it _later_
25
26 This is really a backwards-source-compatibility issue. In newpb, the
27 preferred way of invoking callRemote() is with kwargs exclusively: glyph's
28 felt positional arguments are more fragile. If the client has a
29 RemoteInterface, then they can convert any positional arguments into keyword
30 arguments before sending the request.
31
32 The question is what to do when the client is not using a RemoteInterface.
33 Until recently, callRemote("bar") would try to find a matching RI. I changed
34 that to have callRemote("bar") never use an RI, and instead you would use
35 callRemote(RIFoo['bar']) to indicate that you want argument-checking.
36
37 That makes positional arguments problematic in more situations than they were
38 before. The decision to be made is if the OPEN(call) sequence should provide
39 a way to convey positional args to the server (probably with numeric "names"
40 in the (argname, argvalue) tuples). If we do this, the server (which always
41 has the RemoteInterface) can do the positional-to-keyword mapping. But
42 putting this in the protocol will oblige other implementations to handle them
43 too.
44
45 * change the method-call syntax to include an interfacename
46 DONE
47
48 Scope the method name to the interface. This implies (I think) one of two
49 things:
50
51  callRemote() must take a RemoteInterface argument
52
53  each RemoteReference handles just a single Interface
54
55 Probably the latter, maybe have the RR keep both default RI and a list of
56 all implemented ones, then adapting the RR to a new RI can be a simple copy
57 (and change of the default one) if the Referenceable knows about the RI.
58 Otherwise something on the local side will need to adapt one RI to another.
59 Need to handle reference-counting/DECREF properly for these shared RRs.
60
61 From glyph:
62
63  callRemote(methname, **args) # searches RIs
64  callRemoteInterface(remoteinterface, methname, **args) # single RI
65
66  getRemoteURL(url, *interfaces)
67
68  URL-RRefs should turn into the original Referenceable (in args/results)
69   (map through the factory's table upon receipt)
70
71  URL-RRefs will not survive round trips. leave reference exchange for later.
72   (like def remote_foo(): return GlobalReference(self) )
73
74  move method-invocation code into pb.Referenceable (or IReferenceable
75  adapter). Continue using remote_ prefix for now, but make it a property of
76  that code so it can change easily.
77   <warner> ok, for today I'm just going to stick with remote_foo() as a
78   low-budget decorator, so the current restrictions are 1: subclass
79   pb.Referenceable, 2: implements() a RemoteInterface with method named "foo",
80   3: implement a remote_foo method
81   <warner> and #1 will probably go away within a week or two, to be replaced by
82   #1a: subclass pb.Referenceable OR #1b: register an IReferenceable adapter
83
84  try serializing with ISliceable first, then try IReferenceable. The
85  IReferenceable adapter must implements() some RemoteInterfaces and gets
86  serialized with a MyReferenceSlicer.
87
88 http://svn.twistedmatrix.com/cvs/trunk/pynfo/admin.py?view=markup&rev=44&root=pynfo
89
90 ** use the methods of the RemoteInterface as the "method name"
91 DONE (provisional), using RIFoo['add']
92
93  rr.callRemote(RIFoo.add, **args)
94
95 Nice and concise. However, #twisted doesn't like it, adding/using arbitrary
96 attributes of Interfaces is not clean (think about IFoo.implements colliding
97 with RIFoo.something).
98
99  rr.callRemote(RIFoo['add'], **args)
100  RIFoo(rr).callRemote('add', **args)
101   adaptation, or narrowing?
102
103 <warner> glyph: I'm adding callRemote(RIFoo.bar, **args) to newpb right now
104 <radix> wow.
105 <warner> seemed like a simpler interface than callRemoteInterface("RIFoo",
106 "bar", **args)
107 <radix> warner: Does this mean that IPerspective can be parameterized now?
108 <glyph> warner: bad idea
109 <exarkun> warner: Zope hates you!
110 <glyph> warner: zope interfaces don't support that syntax
111 <slyphon> zi does support multi-adapter syntax
112 <slyphon> but i don't really know what that is
113 <exarkun> warner: callRemote(RIFoo.getDescriptionFor("bar"), *a, **k)
114 <warner> glyph: yeah, I fake it. In RemoteInterfaceClass, I remove those
115 attributes, call InterfaceClass, and then put them all back in
116 <glyph> warner: don't add 'em as attributes
117 <glyph> warner: just fix the result of __getitem__ to add a slot actually
118 refer back to the interface
119 <glyph> radix: the problem is that IFoo['bar'] doesn't point back to IFoo
120 <glyph> warner: even better, make them callable :-)
121 <exarkun> glyph: IFoo['bar'].interface == 'IFoo'
122 <glyph> RIFoo['bar']('hello')
123 <warner> glyph: I was thinking of doing that in a later version of
124 RemoteInterface
125 <glyph> exarkun: >>> type(IFoo['bar'].interface)
126 <glyph> <type 'str'>
127 <exarkun> right
128 <exarkun> 'IFoo'
129 <exarkun> Just look through all the defined interfaces for ones with matching
130 names
131 <glyph> exarkun: ... e.g. *NOT* __main__.IFoo
132 <glyph> exarkun: AAAA you die
133 <radix> hee hee
134 * warner struggles to keep up with his thoughts and those of people around him
135 * glyph realizes he has been given the power to whine
136 <warner> glyph: ok, so with RemoteInterface.__getitem__, you could still do
137 rr.callRemote(RIFoo.bar, **kw), right?
138 <warner> was your objection to the interface or to the implementation?
139 <itamar> I really don't think you should add attributes to the interface
140 <warner> ok
141 <warner> I need to stash a table of method schemas somewhere
142 <itamar> just make __getitem__ return better type of object
143 <itamar> and ideally if this is generic we can get it into upstream
144 <exarkun> Is there a reason Method.interface isn't a fully qualified name?
145 <itamar> not necessarily
146 <itamar> I have commit access to zope.interface
147 <itamar> if you have any features you want added, post to
148 interface-dev@zope.org mailing list
149 <itamar> and if Jim Fulton is ok with them I can add them for you
150 <warner> hmm
151 <warner> does using RIFoo.bar to designate a remote method seem reasonable?
152 <warner> I could always adapt it to something inside callRemote
153 <warner> something PB-specific, that is
154 <warner> but that adapter would have to be able to pull a few attributes off
155 the method (name, schema, reference to the enclosing RemoteInterface)
156 <warner> and we're really talking about __getattr__ here, not __getitem__,
157 right?
158 <exarkun> for x.y yes
159 <itamar> no, I don't think that's a good idea
160 <itamar> interfaces have all kinds od methods on them already, for
161 introspection purposes
162 <itamar> namespace clashes are the suck
163 <itamar> unless RIFoo isn't really an Interface
164 <itamar> hm
165 <itamar> how about if it were a wrapper around a regular Interface?
166 <warner> yeah, RemoteInterfaces are kind of a special case
167 <itamar> RIFoo(IFoo, publishedMethods=['doThis', 'doThat'])
168 <itamar> s/RIFoo/RIFoo = RemoteInterface(/
169 <exarkun> I'm confused. Why should you have to specify which methods are
170 published?
171 <itamar> SECURITY!
172 <itamar> not actually necessary though, no
173 <itamar> and may be overkill
174 <warner> the only reason I have it derive from Interface is so that we can do
175 neat adapter tricks in the future
176 <itamar> that's not contradictory
177 <itamar> RIFoo(x) would still be able to do magic
178 <itamar> you wouldn't be able to check if an object provides RIFoo, though
179 <itamar> which kinda sucks
180 <itamar> but in any case I am against RIFoo.bar
181 <warner> pity, it makes the callRemote syntax very clean
182 <radix> hm
183 <radix> So how come it's a RemoteInterface and not an Interface, anyway?
184 <radix> I mean, how come that needs to be done explicitly. Can't you just
185 write a serializer for Interface itself?
186
187 * warner goes to figure out where the RemoteInterface discussion went after he
188   got distracted
189 <warner> maybe I should make RemoteInterface a totally separate class and just
190 implement a couple of Interface-like methods
191 <warner> cause rr.callRemote(IFoo.bar, a=1) just feels so clean
192 <Jerub> warner: why not IFoo(rr).bar(a=1) ?
193 <warner> hmm, also a possibility
194 <radix> well
195 <radix> IFoo(rr).callRemote('bar')
196 <radix> or RIFoo, or whatever
197 <Jerub> hold on, what does rr inherit from?
198 <warner> RemoteReference
199 <radix> it's a RemoteReference
200 <Jerub> then why not IFoo(rr) /
201 <warner> I'm keeping a strong distinction between local interfaces and remote
202 ones
203 <Jerub> ah, oka.y
204 <radix> warner: right, you can still do RIFoo
205 <warner> ILocal(a).meth(args) is an immediate function call
206 <Jerub> in that case, I prefer rr.callRemote(IFoo.bar, a=1)
207 <radix> .meth( is definitely bad, we need callRemote
208 <warner> rr.callRemote("meth", args) returns a deferred
209 <Jerub> radix: I don't like from foo import IFoo, RIFoo
210 <warner> you probably wouldn't have both an IFoo and an RIFoo
211 <radix> warner: well, look at it this way: IFoo(rr).callRemote('foo') still
212 makes it obvious that IFoo isn't local
213 <radix> warner: you could implement RemoteReferen.__conform__ to implement it
214 <warner> radix: I'm thinking of providing some kind of other class that would
215 allow .meth() to work (without the callRemote), but it wouldn't be the default
216 <radix> plus, IFoo(rr) is how you use interfaces normally, and callRemote is
217 how you make remote calls normally, so it seems that's the best way to do
218 interfaces + PB
219 <warner> hmm
220 <warner> in that case the object returned by IFoo(rr) is just rr with a tag
221 that sets the "default interface name"
222 <radix> right
223 <warner> and callRemote(methname) looks in that default interface before
224 looking anywhere else
225 <warner> for some reason I want to get rid of the stringyness of the method
226 name
227 <warner> and the original syntax (callRemoteInterface('RIFoo', 'methname',
228 args)) felt too verbose
229 <radix> warner: well, isn't that what your optional .meth thing is for?
230 <radix> yes, I don't like that either
231 <warner> using callRemote(RIFoo.bar, args) means I can just switch on the
232 _name= argument being either a string or a (whatever) that's contained in a
233 RemoteInterface
234 <warner> a lot of it comes down to how adapters would be most useful when
235 dealing with remote objects
236 <warner> and to what extent remote interfaces should be interchangeable with
237 local ones
238 <radix> good point. I have never had a use case where I wanted to adapt a
239 remote object, I don't think
240 <radix> however, I have had use cases to send interfaces across the wire
241 <radix> e.g. having a parameterized portal.login() interface
242 <warner> that'll be different, just callRemote('foo', RIFoo)
243 <radix> yeah.
244 <warner> the current issue is whether to pass them by reference or by value
245 <radix> eugh
246 <radix> Can you explain it without using those words? :)
247 <warner> hmm
248 <radix> Do you mean, Referenceable style vs Copyable style?
249 <warner> at the moment, when you send a Referenceable across the wire, the
250 id-number is accompanied with a list of strings that designate which
251 RemoteInterfaces the original claims to provide
252 <warner> the receiving end looks up each string in a local table, and
253 populates the RemoteReference with a list of RemoteInterface classes
254 <warner> the table is populated by metaclass magic that runs when a 'class
255 RIFoo(RemoteInterface)' definition is complete
256 <radix> ok
257 <radix> so a RemoteInterface is simply serialized as its qual(), right?
258 <warner> so as long as both sides include the same RIFoo definition, they'll
259 wind up with compatible remote interfaces, defining the same method names,
260 same method schemas, etc
261 <warner> effectively
262 <warner> you can't just send a RemoteInterface across the wire right now, but
263 it would be easy to add
264 <warner> the places where they are used (sending a Referenceable across the
265 wire) all special case them
266 <radix> ok, and you're considering actually writing a serializer for them that
267 sends all the information to totally reconstruct it on the other side without
268 having the definiton
269 <warner> yes
270 <warner> or having some kind of debug method which give you that
271 <radix> I'd say, do it the way you're doing it now until someone comes up with
272 a use case for actually sending it...
273 <warner> right
274 <warner> the only case I can come up with is some sort of generic object
275 browser debug tool
276 <warner> everything else turns into a form of version negotiation which is
277 better handled elsewhere
278 <warner> hmm
279 <warner> so RIFoo(rr).callRemote('bar', **kw)
280 <warner> I guess that's not too ugly
281 <radix> That's my vote. :)
282 <warner> one thing it lacks is the ability to cleanly state that if 'bar'
283 doesn't exist in RIFoo then it should signal an error
284 <warner> whereas callRemote(RIFoo.bar, **kw) would give you an AttributeError
285 before callRemote ever got called
286 <warner> i.e. "make it impossible to express the incorrect usage"
287 <radix> mmmh
288 <radix> warner: but you _can_ check it immediately when it's called
289 <warner> in the direction I was heading, callRemote(str) would just send the
290 method request and let the far end deal with it, no schema-checking involved
291 <radix> warner: which, 99% of the time, is effectively the same time as
292 IFoo.bar would happen
293 <warner> whereas callRemote(RIFoo.bar) would indicate that you want schema
294 checking
295 <warner> yeah, true
296 <radix> hm.
297 <warner> (that last feature is what allowed callRemote and callRemoteInterface
298 to be merged)
299 <warner> or, I could say that the normal RemoteReference is "untyped" and does
300 not do schema checking
301 <warner> but adapting one to a RemoteInterface results in a
302 TypedRemoteReference which does do schema checking
303 <warner> and which refuses to be invoked with method names that are not in the
304 schema
305 <radix> warner: we-ell
306 <radix> warner: doing method existence checking is cool
307 <radix> warner: but I think tying any further "schema checking" to adaptation
308 is a bad idea
309 <warner> yeah, that's my hunch too
310 <warner> which is why I'd rather not use adapters to express the scope of the
311 method name (which RemoteInterface it is supposed to be a part of)
312 <radix> warner: well, I don't think tying it to callRemote(RIFoo.methName)
313 would be a good idea just the same
314 <warner> hm
315 <warner> so that leaves rr.callRemote(RIFoo['add']) and
316 rr.callRemoteInterface(RIFoo, 'add')
317 <radix> OTOH, I'm inclined to think schema checking should happen by default
318 <radix> It's just a the matter of where it's parameterized
319 <warner> yeah, it's just that the "default" case (rr.callRemote('name')) needs
320 to work when there aren't any RemoteInterfaces declared
321 <radix> warner: oh
322 <warner> but if we want to encourage people to use the schemas, then we need
323 to make that case simple and concise
324 * radix goes over the issue in his head again
325 <radix> Yes, I think I still have the same position.
326 <warner> which one? :)
327 <radix> IFoo(rr).callRemote("foo"); which would do schema checking because
328 schema checking is on by default when it's possible
329 <warner> using an adaptation-like construct to declare a scope of the method
330 name that comes later
331 <radix> well, it _is_ adaptation, I think.
332 <radix> Adaptation always has plugged in behavior, we're just adding a bit
333 more :)
334 <warner> heh
335 <warner> it is a narrowing of capability
336 <radix> hmm, how do you mean?
337 <warner> rr.callRemote("foo") will do the same thing
338 <warner> but rr.callRemote("foo") can be used without the remote interfaces
339 <radix> I think I lost you.
340 <warner> if rr has any RIs defined, it will try to use them (and therefore
341 complain if "foo" does not exist in any of them, or if the schema is violated)
342 <radix> Oh. That's strange.
343 <radix> So it's really quite different from how interfaces regularly work...
344 <warner> yeah
345 <warner> except that if you were feeling clever you could use them the normal
346 way
347 <radix> Well, my inclination is to make them work as similarly as possible.
348 <warner> "I have a remote reference to something that implements RIFoo, but I
349 want to use it in some other way"
350 <radix> s/possible/practical/
351 <warner> then IBar(rr) or RIBar(rr) would wrap rr in something that knows how
352 to translate Bar methods into RIFoo remote methods
353 <radix> Maybe it's not practical to make them very similar.
354 <radix> I see.
355
356 rr.callRemote(RIFoo.add, **kw)
357 rr.callRemote(RIFoo['add'], **kw)
358 RIFoo(rr).callRemote('add', **kw)
359
360 I like the second one. Normal Interfaces behave like a dict, so IFoo['add']
361 gets you the method-describing object (z.i.i.Method). My RemoteInterfaces
362 don't do that right now (because I remove the attributes before handing the
363 RI to z.i), but I could probably fix that. I could either add attributes to
364 the Method or hook __getitem__ to return something other than a Method
365 (maybe a RemoteMethodSchema).
366
367 Those Method objects have a .getSignatureInfo() which provides almost
368 everything I need to construct the RemoteMethodSchema. Perhaps I should
369 post-process Methods rather than pre-process the RemoteInterface. I can't
370 tell how to use the return value trick, and it looks like the function may
371 be discarded entirely once the Method is created, so this approach may not
372 work.
373
374 On the server side (Referenceable), subclassing Interface is nice because it
375 provides adapters and implements() queries.
376
377 On the client side (RemoteReference), subclassing Interface is a hassle: I
378 don't think adapters are as useful, but getting at a method (as an attribute
379 of the RI) is important. We have to bypass most of Interface to parse the
380 method definitions differently.
381
382 * create UnslicerRegistry, registerUnslicer
383 DONE (PROVISIONAL), flat registry (therefore problematic for len(opentype)>1)
384
385 consider adopting the existing collection API (getChild, putChild) for this,
386 or maybe allow registerUnslicer() to take a callable which behaves kind of
387 like a twisted.web isLeaf=1 resource (stop walking the tree, give all index
388 tokens to the isLeaf=1 node)
389
390 also some APIs to get a list of everything in the registry
391
392 * use metaclass to auto-register RemoteCopy classes
393 DONE
394
395 ** use metaclass to auto-register Unslicer classes
396 DONE
397
398 ** and maybe Slicer classes too
399 DONE with name 'slices', perhaps change to 'slicerForClasses'?
400
401  class FailureSlicer(slicer.BaseSlicer):
402      classname = "twisted.python.failure.Failure"
403      slicerForClasses = (failure.Failure,) # triggers auto-register
404
405 ** various registry approaches
406 DONE
407
408 There are currently three kinds of registries used in banana/newpb:
409
410  RemoteInterface <-> interface name
411  class/type -> Slicer (-> opentype) -> Unslicer (-> class/type)
412  Copyable subclass -> copyable-opentype -> RemoteCopy subclass
413
414 There are two basic approaches to representing the mappings that these
415 registries implement. The first is implicit, where the local objects are
416 subclassed from Sliceable or Copyable or RemoteInterface and have attributes
417 to define the wire-side strings that represent them. On the receiving side,
418 we make extensive use of metaclasses to perform automatic registration
419 (taking names from class attributes and mapping them to the factory or
420 RemoteInterface used to create the remote version).
421
422 The second approach is explicit, where pb.registerRemoteInterface,
423 pb.registerRemoteCopy, and pb.registerUnslicer are used to establish the
424 receiving-side mapping. There isn't a clean way to do it explicitly on the
425 sending side, since we already have instances whose classes can give us
426 whatever information we want.
427
428 The advantage of implicit is simplicity: no more questions about why my
429 pb.RemoteCopy is giving "not unserializable" errors. The mere act of
430 importing a module is enough to let PB create instances of its classes.
431
432 The advantage of doing it explicitly is to remind the user about the
433 existence of those maps, because the factory classes in the receiving map is
434 precisely equal to the user's exposure (from a security point of view). See
435 the E paper on secure-serialization for some useful concepts.
436
437 A disadvantage of implicit is that you can't quite be sure what, exactly,
438 you're exposed to: the registrations take place all over the place.
439
440 To make explicit not so painful, we can use quotient's .wsv files
441 (whitespace-separated values) which map from class to string and back again.
442 The file could list fully-qualified classname, wire-side string, and
443 receiving factory class on each line. The Broker (or rather the RootSlicer
444 and RootUnslicer) would be given a set of .wsv files to define their
445 mapping. It would get all the registrations at once (instead of having them
446 scattered about). They could also demand-load the receive-side factory
447 classes.
448
449 For now, go implicit. Put off the decision until we have some more
450 experience with using newpb.
451
452 * move from VocabSlicer sequence to ADDVOCAB/DELVOCAB tokens
453
454 Requires a .wantVocabString flag in the parser, which is kind of icky but
455 fixes the annoying asymmetry between set (vocab sequence) and get (VOCAB
456 token). Might want a CLEARVOCAB token too.
457
458 On second thought, this won't work. There isn't room for both a vocab number
459 and a variable-length string in a single token. It must be an open sequence.
460 However, it could be an add/del/set-vocab sequence, allowing the vocab to be
461 modified incrementally.
462
463 ** VOCABize interface/method names
464
465 One possibility is to make a list of all strings used by all known
466 RemoteInterfaces and all their methods, then send it at broker connection
467 time as the initial vocab map. A better one (maybe) is to somehow track what
468 we send and add a word to the vocab once we've sent it more than three
469 times.
470
471 Maybe vocabize the pairs, as "ri/name1","ri/name2", etc, or maybe do them
472 separately. Should do some handwaving math to figure out which is better.
473
474 * nail down some useful schema syntaxes
475
476 This has two parts: parsing something like a __schema__ class attribute (see
477 the sketches in schema.xhtml) into a tree of FooConstraint objects, and
478 deciding how to retrieve schemas at runtime from things like the object being
479 serialized or the object being called from afar. To be most useful, the
480 syntax needs to mesh nicely (read "is identical to") things like formless and
481 (maybe?) atop or whatever has replaced the high-density highly-structured
482 save-to-disk scheme that twisted.world used to do.
483
484 Some lingering questions in this area:
485
486  When an object has a remotely-invokable method, where does the appropriate
487  MethodConstraint come from? Some possibilities:
488
489   an attribute of the method itself: obj.method.__schema__
490
491   from inside a __schema__ attribute of the object's class
492
493   from inside a __schema__ attribute of an Interface (which?) that the object
494   implements
495
496  Likewise, when a caller holding a RemoteReference invokes a method on it, it
497  would be nice to enforce a schema on the arguments they are sending to the
498  far end ("be conservative in what you send"). Where should this schema come
499  from? It is likely that the sender only knows an Interface for their
500  RemoteReference.
501
502  When PB determines that an object wants to be copied by value instead of by
503  reference (pb.Copyable subclass, Copyable(obj), schema says so), where
504  should it find a schema to define what exactly gets copied over? A class
505  attribute of the object's class would make sense: most objects would do
506  this, some could override jellyFor to get more control, and others could
507  override something else to push a new Slicer on the stack and do streaming
508  serialization. Whatever the approach, it needs to be paralleled by the
509  receiving side's unjellyableRegistry.
510
511 * RemoteInterface instances should have an "RI-" prefix instead of "I-"
512
513 DONE
514
515 * merge my RemoteInterface syntax with zope.interface's
516
517 I hacked up a syntax for how method definitions are parsed in
518 RemoteInterface objects. That syntax isn't compatible with the one
519 zope.interface uses for local methods, so I just delete them from the
520 attribute dictionary to avoid causing z.i indigestion. It would be nice if
521 they were compatible so I didn't have to do that. This basically translates
522 into identifying the nifty extra flags (like priority classes, no-response)
523 that we want on these methods and finding a z.i-compatible way to implement
524 them. It also means thinking of SOAP/XML-RPC schemas and having a syntax
525 that can represent everything at once.
526
527
528 * use adapters to enable pass-by-reference or pass-by-value
529
530 It should be possible to pass a reference with variable forms:
531
532  rr.callRemote("foo", 1, Reference(obj))
533  rr.callRemote("bar", 2, Copy(obj))
534
535 This should probably adapt the object to IReferenceable or ICopyable, which
536 are like ISliceable except they can pass the object by reference or by
537 value. The slicing process should be:
538
539  look up the type() in a table: this handles all basic types
540  else adapt the object to ISliceable, use the result
541  else raise an Unsliceable exception
542   (and point the user to the docs on how to fix it)
543
544 The adapter returned by IReferenceable or ICopyable should implement
545 ISliceable, so no further adaptation will be done.
546
547 * remove 'copy' prefix from remotecopy banana type names?
548
549 <glyph> warner: did we ever finish our conversation on the usefulness of the
550 (copy foo blah) namespace rather than just (foo blah)?
551 <warner> glyph: no, I don't think we did
552 <glyph> warner: do you still have (copy foo blah)?
553 <warner> glyph: yup
554 <warner> so far, it seems to make some things easier
555 <warner> glyph: the sender can subclass pb.Copyable and not write any new
556 code, while the receiver can write an Unslicer and do a registerRemoteCopy
557 <warner> glyph: instead of the sender writing a whole slicer and the receiver
558 registering at the top-level
559 <glyph> warner: aah
560 <warner> glyph: although the fact that it's easier that way may be an artifact
561 of my sucky registration scheme
562 <glyph> warner: so the advantage is in avoiding registration of each new
563 unslicer token?
564 <glyph> warner: yes. I'm thinking that a metaclass will handily remove the
565 need for extra junk in the protocol ;)
566 <warner> well, the real reason is my phobia about namespace purity, of course
567 <glyph> warner: That's what the dots are for
568 <warner> but ease of dispatch is also important
569 <glyph> warner: I'm concerned about it because I consider my use of the same
570 idiom in the first version of PB to be a serious wart
571 * warner nods
572 <warner> I will put together a list of my reasoning
573 <glyph> warner: I think it's likely that PB implementors in other languages
574 are going to want to introduce new standard "builtin" types; our "builtins"
575 shouldn't be limited to python's provided data structures
576 <moshez> glyph: wait
577 <warner> ok
578 <moshez> glyph: are you talking of banana types
579 <moshez> glyph: or really PB
580 <warner> in which case (copy blah blah) is a non-builtin type, while
581 (type-foo) is a builtin type
582 <glyph> warner: plus, our namespaces are already quite well separated, I can
583 tell you I will never be declaring new types outside of quotient.* and
584 twisted.* :)
585 <warner> moshez: this is mostly banana (or what used to be jelly, really)
586 <glyph> warner: my inclination is to standardize by convention
587 <glyph> warner: *.* is a non-builtin type, [~.] is a builtin
588 <moshez> glyph: ?
589 <glyph> sorry [^.]*
590 <glyph> my regular expressions and shell globs are totally confused but you
591 know what I mean
592 <glyph> moshez: yes
593 <moshez> glyph: hrm
594 <saph_w> glyph: you're making crazy anime faces
595 <moshez> glyph: why do we need any non-Python builtin types
596 <glyph> moshez: because I want to destroy SOAP, and doing that means working
597 with people I don't like
598 <glyph> moshez: outside of python
599 <moshez> glyph: I meant, "what specific types"
600 <moshez> I'd appreciate a blog on that
601
602 * have Copyable/RemoteCopy default to __getstate__/__setstate__?
603
604 At the moment, the default implementations of getStateToCopy() and
605 setCopyableState() get and set __dict__ directly. Should the default instead
606 be to call __getstate__() or __setstate__()?
607
608 * make slicer/unslicers for pb.RemoteInterfaces
609
610 exarkun's use case requires these Interfaces to be passable by reference
611 (i.e. by name). It would also be interesting to let them be passed (and
612 requested!) by value, so you can ask a remote peer exactly what their
613 objects will respond to (the method names, the argument values, the return
614 value). This also requires that constraints be serializable.
615
616 do this, should be referenceable (round-trip should return the same object),
617 should use the same registration lookup that RemoteReference(interfacelist)
618 uses
619
620 * investigate decref/Referenceable race
621
622 Any object that includes some state when it is first sent across the wire
623 needs more thought. The far end could drop the last reference (at time t=1)
624 while a method is still pending that wants to send back the same object. If
625 the method finishes at time t=2 but the decref isn't received until t=3, the
626 object will be sent across the wire without the state, and the far end will
627 receive it for the "first" time without that associated state.
628
629 This kind of conserve-bandwidth optimization may be a bad idea. Or there
630 might be a reasonable way to deal with it (maybe request the state if it
631 wasn't sent and the recipient needs it, and delay delivery of the object
632 until the state arrives).
633
634 DONE, the RemoteReference is held until the decref has been acked. As long as
635 the methods are executed in-order, this will prevent the race. TODO:
636 third-party references (and other things that can cause out-of-order
637 execution) could mess this up.
638
639 * sketch out how to implement glyph's crazy non-compressed sexpr encoding
640
641 * consider a smaller scope for OPEN-counter reference numbers
642
643 For newpb, we moved to implicit reference numbers (counting OPEN tags
644 instead of putting a number in the OPEN tag) because we didn't want to burn
645 so much bandwidth: it isn't feasible to predict whether your object will
646 need to be referenced in the future, so you always have to be prepared to
647 reference it, so we always burn the memory to keep track of them (generally
648 in a ScopedSlicer subclass). If we used explicit refids then we'd have to
649 burn the bandwidth too.
650
651 The sorta-problem is that these numbers will grow without bound as long as
652 the connection remains open. After a few hours of sending 100-byte objects
653 over a 100MB connection, you'll hit 1G-references and will have to start
654 sending them as LONGINT tokens, which is annoying and slightly verbose (say
655 3 or 4 bytes of number instead of 1 or 2). You never keep track of that many
656 actual objects, because the references do not outlive their parent
657 ScopedSlicer.
658
659 The fact that the references themselves are scoped to the ScopedSlicer
660 suggests that the reference numbers could be too. Each ScopedSlicer would
661 track the number of OPEN tokens emitted (actually the number of
662 slicerForObject calls made, except you'd want to use a different method to
663 make sure that children who return a Slicer themselves don't corrupt the
664 OPEN count).
665
666 This requires careful synchronization between the ScopedSlicers on one end
667 and the ScopedUnslicers on the other. I suspect it would be slightly
668 fragile.
669
670 One sorta-benefit would be that a somewhat human-readable sexpr-based
671 encoding would be even more human readable if the reference numbers stayed
672 small (you could visually correlate objects and references more easily). The
673 ScopedSlicer's open-parenthesis could be represented with a curly brace or
674 something, then the refNN number would refer to the NN'th left-paren from
675 the last left-brace. It would also make it clear that the recipient will not
676 care about objects outside that scope.
677
678 * implement the FDSlicer
679
680 Over a unix socket, you can pass fds. exarkun had a presentation at PyCon04
681 describing the use of this to implement live application upgrade. I think
682 that we could make a simple FDSlicer to hide the complexity of the
683 out-of-band part of the communication.
684
685 class Server(unix.Server):
686     def sendFileDescriptors(self, fileno, data="Filler"):
687         """
688         @param fileno: An iterable of the file descriptors to pass.
689         """
690         payload = struct.pack("%di" % len(fileno), *fileno)
691         r = sendmsg(self.fileno(), data, 0, (socket.SOL_SOCKET, SCM_RIGHTS, payload))
692         return r
693
694 class Client(unix.Client):
695     def doRead(self):
696         if not self.connected:
697             return
698         try:
699             msg, flags, ancillary = recvmsg(self.fileno())
700         except:
701             log.msg('recvmsg():')
702             log.err()
703         else:
704             buf = ancillary[0][2]
705             fds = [] 
706             while buf:
707                 fd, buf = buf[:4], buf[4:]
708                 fds.append(struct.unpack("i", fd)[0])
709             try:
710                 self.protocol.fileDescriptorsReceived(fds)
711             except:
712                 log.msg('protocol.fileDescriptorsReceived')
713                 log.err()
714         return unix.Client.doRead(self)
715
716 * implement AsyncDeferred returns
717
718 dash wanted to implement a TransferrableReference object with a scheme that
719 would require creating a new connection (to a third-party Broker) during
720 ReferenceUnslicer.receiveClose . This would cause the object deserialization
721 to be asynchronous.
722
723 At the moment, Unslicers can return a Deferred from their receiveClose
724 method. This is used by immutable containers (like tuples) to indicate that
725 their object cannot be created yet. Other containers know to watch for these
726 Deferreds and add a callback which will update their own entries
727 appropriately. The implicit requirement is that all these Deferreds fire
728 before the top-level parent object (usually a CallUnslicer) finishes. This
729 allows for circular references involving immutable containers to be resolved
730 into the final object graph before the target method is invoked.
731
732 To accomodate Deferreds which will fire at arbitrary points in the future,
733 it would be useful to create a marker subclass named AsyncDeferred. If an
734 unslicer returns such an object, the container parent starts by treating it
735 like a regular Deferred, but it also knows that its object is not
736 "complete", and therefore returns an AsyncDeferred of its own. When the
737 child completes, the parent can complete, etc. The difference between the
738 two types: Deferred means that the object will be complete before the
739 top-level parent is finished, AsyncDeferred makes claims about when the
740 object will be finished.
741
742 CallUnslicer would know that if any of its arguments are Deferreds or
743 AsyncDeferreds then it need to hold off on the broker.doCall until all those
744 Deferreds have fired. Top-level objects are not required to differentiate
745 between the two types, because they do not return an object to an enclosing
746 parent (the CallUnslicer is a child of the RootUnslicer, but it always
747 returns None).
748
749 Other issues: we'll need a schema to let you say whether you'll accept these
750 late-bound objects or not (because if you do accept them, you won't be able
751 to impose the same sorts of type-checks as you would on immediate objects).
752 Also this will impact the in-order-invocation promises of PB method calls,
753 so we may need to implement the "it is ok to run this asynchronously" flag
754 first, then require that TransferrableReference objects are only passed to
755 methods with the flag set.
756
757 Also, it may not be necessary to have a marker subclass of Deferred: perhaps
758 _any_ Deferred which arrives from a child is an indication that the object
759 will not be available until an unknown time in the future, and obligates the
760 parent to return another Deferred upwards (even though their object could be
761 created synchronously). Or, it might be better to implement this some other
762 way, perhaps separating "here is my object" from "here is a Deferred that
763 will fire when my object is complete", like a call to
764 parent.addDependency(self.deferred) or something.
765
766 DONE, needs testing
767
768 * TransferrableReference
769
770 class MyThing(pb.Referenceable): pass
771 r1 = MyThing()
772 r2 = Facet(r1)
773 g1 = Global(r1)
774 class MyGlobalThing(pb.GloballyReferenceable): pass
775 g2 = MyGlobalThing()
776 g3 = Facet(g2)
777
778 broker.setLocation("pb://hostname.com:8044")
779
780 rem.callRemote("m1", r1) # limited to just this connection
781 rem.callRemote("m2", Global(r1)) # can be published
782 g3 = Global(r1)
783 rem.callRemote("m3", g1) # can also be published..
784 g1.revoke() # but since we remember it, it can be revoked too
785 g1.restrict() # and, as a Facet, we can revoke some functionality but not all
786
787 rem.callRemote("m1", g2) # can be published
788
789 E tarball: jsrc/net/captp/tables/NearGiftTable
790
791 issues:
792  1: when A sends a reference on B to C, C's messages to the object
793  referenced must arrive after any messages A sent before the reference forks
794
795  in particular, if A does:
796   B.callRemote("1", hugestring)
797   B.callRemote("2_makeYourSelfSecure", args)
798   C.callRemote("3_transfer", B)
799
800  and C does B.callRemote("4_breakIntoYou") as soon as it gets the reference,
801  then the A->B queue looks like (1, 2), and the A->C queue looks like (3).
802  The transfer message can be fast, and the resulting 4 message could be
803  delivered to B before the A->B queue manages to deliver 2.
804
805  2: an object which get passed through multiple external brokers and
806  eventually comes home must be recognized as a local object
807
808  3: Copyables that contain RemoteReferences must be passable between hosts
809
810 E cannot do all three of these at once
811 http://www.erights.org/elib/distrib/captp/WormholeOp.html
812
813 I think that it's ok to tell people who want this guarantee to explicitly
814 serialize it like this:
815
816  B.callRemote("1", hugestring)
817  d = B.callRemote("2_makeYourSelfSecure", args)
818  d.addCallback(lambda res: C.callRemote("3_transfer", B))
819
820 Note that E might not require that method calls even have a return value, so
821 they might not have had a convenient way to express this enforced
822 serialization.
823
824 ** more thoughts
825
826 To enforce the partial-ordering, you could do the equivalent of:
827  A:
828   B.callRemote("1", hugestring)
829   B.callRemote("2_makeYourSelfSecure", args)
830   nonce = makeNonce()
831   B.callRemote("makeYourSelfAvailableAs", nonce)
832   C.callRemote("3_transfer", (nonce, B.name))
833  C:
834   B.callRemote("4_breakIntoYou")
835
836 C uses the nonce when it connects to B. It knows the name of the reference,
837 so it can compare it against some other reference to the same thing, but it
838 can't actually use that name alone to get access.
839
840 When the connection request arrives at B, it sees B.name (which is also
841 unguessable), so that gives it reason to believe that it should queue C's
842 request (that it isn't just a DoS attack). It queues it until it sees A's
843 request to makeYourSelfAvailableAs with the matching nonce. Once that
844 happens, it can provide the reference back to C.
845
846 This implies that C won't be able to send *any* messages to B until that
847 handshake has completed. It might be desireable to avoid the extra round-trip
848 this would require.
849
850 ** more thoughts
851
852  url = PBServerFactory.registerReference(ref, name=None)
853   creates human-readable URLs or random identifiers
854
855 the factory keeps a bidirectional mapping of names and Referenceables
856
857 when a Referenceable gets serialized, if the factory's table doesn't have a
858 name for it, the factory creates a random one. This entry in the table is
859 kept alive by two things:
860
861  a live reference by one of the factory's Brokers
862  an entry in a Broker's "gift table"
863
864 When a RemoteReference gets serialized (and it doesn't point back to the
865 receiving Broker, and thus get turned into a your-reference sequence),
866
867 <warner> A->C: "I'm going to send somebody a reference to you, incref your
868          gift table", C->A: roger that, here's a gift nonce
869 <warner> A->B: "here's Carol's reference: URL plus nonce"
870 <warner> B->C: "I want a liveref to your 'Carol' object, here's my ticket
871          (nonce)", C->B: "ok, ticket redeemed, here's your liveref"
872
873 once more, without nonces:
874  A->C: "I'm going to send somebody a reference to you, incref your
875        gift table", C->A: roger that
876  A->B: "here's Carol's reference: URL"
877  B->C: "I want a liveref to your 'Carol' object", C->B: "ok, here's your
878        liveref"
879
880 really:
881  on A: c.vat.callRemote("giftYourReference", c).addCallback(step2)
882        c is serialized as (your-reference, clid)
883  on C: vat.remote_giftYourReference(which): self.table[which] += 1; return
884  on A: step2: b.introduce(c)
885        c is serialized as (their-reference, url)
886  on B: deserialization sees their-reference
887        newvat = makeConnection(URL)
888        newvat.callRemote("redeemGift", URL).addCallback(step3)
889  on C: vat.remote_redeemGift(URL):
890            ref = self.urls[URL]; self.table[ref] -= 1; return ref
891        ref is serialized as (my-reference, clid)
892  on B: step3(c): b.remote_introduce(c)
893
894 problem: if alice sends a thousand copies, that means these 5 messages are
895 each send a thousand times. The makeConnection is cached, but the rest are
896 not. We don't rememeber that we've already made this gift before, that the
897 other end probably still has it. Hm, but we also don't know that they didn't
898 lose it already.
899
900 ** ok, a plan:
901
902 concern 1: objects must be kept alive as long as there is a RemoteReference
903 to them.
904
905 concern 2: we should be able to tell when an object is being sent for the
906 first time, to add metadata (interface list, public URL) that would be
907 expensive to add to every occurrence.
908
909  each (my-reference) sent over the wire increases the broker's refcount on
910  both ends.
911
912  the receiving Broker retains a weakref to the RemoteReference, and retains a
913  copy of the metadata necessary to create it in the clid table (basically the
914  entire contents of the RemoteReference). When the weakref expires, it marks
915  the clid entry as "pending-free", and sends a decref(clid,N) to the other
916  Broker. The decref is actually sent with broker.callRemote("decref", clid,
917  N), so it can be acked.
918
919  the sending broker gets the decref and reduces its count by N. If another
920  reference was sent recently, this count may not drop all the way to zero,
921  indicating there is a reference "in flight" and the far end should be ready
922  to deal with it (by making a new RemoteReference with the same properties as
923  the old one). If N!=0, it returns False to indicate that this was not the
924  last decref message for the clid. If N==0, it returns True, since it is the
925  last decref, and removes the entry from its table. Once remote_decref
926  returns True, the clid is retired.
927
928  the receiving broker receives the ack from the decref. If the ack says
929  last==True, the clid table entry is freed. If it says last==False, then
930  there should have been another (my-reference) received before the ack, so
931  the refcount should be non-zero.
932
933  message sequence:
934
935    A-> : (my-reference clid metadata)  [A.myrefs[clid].refcount++ = 1]
936    A-> : (my-reference clid)  [A.myrefs[clid].refcount++ = 2]
937     ->B: receives my-ref, creates RR, B.yourrefs[clid].refcount++ = 1
938     ->B: receives my-ref, B.yourrefs[clid].refcount++ = 2
939        :  time passes, B sees the reference go away
940     <-B: d=brokerA.callRemote("decref", clid, B.yourrefs[clid].refcount)
941          B.yourrefs[clid].refcount = 0; d.addCallback(B.checkref, clid)
942    A-> : (my-reference clid)  [A.myrefs[clid].refcount++ = 3]
943    A<- : receives decref, A.myrefs[clid].refcount -= 2, now =1, returns False
944     ->B: receives my-ref, re-creates RR, B.yourrefs[clid].refcount++ = 1
945     ->B: receives ack(False), B.checkref asserts refcount != 0
946        :  time passes, B sees the reference go away again
947     <-B: d=brokerA.callRemote("decref", clid, B.yourrefs[clid].refcount)
948          B.yourrefs[clid].refcount = 0; d.addCallback(B.checkref, clid)
949    A<- : receives decref, A.myrefs[clid].refcount -= 1, now =0, returns True
950          del A.myrefs[clid]
951     ->B: receives ack(True), B.checkref asserts refcount==0
952          del B.yourrefs[clid]
953
954 B retains the RemoteReference data until it receives confirmation from A.
955 Therefore whenever A sends a reference that doesn't already exist in the clid
956 table, it is sending it to a B that doesn't know about that reference, so it
957 needs to send the metadata.
958
959 concern 3: in the three-party exchange, Carol must be kept alive until Bob
960 has established a reference to her, even if Alice drops her carol-reference
961 immediately after sending the introduction to Bob.
962
963 (my-reference, clid, [interfaces, public URL])
964 (your-reference, clid)
965 (their-reference, URL)
966
967 Serializing a their-reference causes an entry to be placed in the Broker's
968 .theirrefs[URL] table. Each time a their-reference is sent, the entry's
969 refcount is incremented.
970
971 Receiving a their-reference may initiate a PB connection to the target,
972 followed by a getNamedReference request. When this completes (or if the
973 reference was already available), the recipient sends a decgift message to
974 the sender. This message includes a count, so multiple instances of the same
975 gift can be acked as a group.
976
977 The .theirrefs entry retains a reference to the sender's RemoteReference, so
978 it cannot go away until the gift is acked.
979
980 DONE, gifts are implemented, we punted on partial-ordering
981
982 *** security, DoS
983
984 Bob can force Alice to hold on to a reference to Carol, as long as both
985 connections are open, by never acknowledging the gift.
986
987 Alice can cause Bob to open up TCP connections to arbitrary hosts and ports,
988 by sending third-party references to him, although the only protocol those
989 connections will speak is PB.
990
991 Using yURLs and StartTLS should be enough to secure and authenticate the
992 connections.
993
994 *** partial-ordering
995
996 If we need it, the gift (their-reference message) can include a nonce, Alice
997 sends a makeYourSelfAvailableAs message to Carol with the nonce, and Bob must
998 do a new getReference with the nonce.
999
1000 Kragen came up with a good use-case for partial-ordering:
1001  A:
1002   B.callRemote("updateDocument", bigDocument)
1003   C.callRemote("pleaseReviewLatest", B)
1004  C:
1005   B.callRemote("getLatestDocument")
1006
1007
1008 * PBService / Tub
1009
1010 Really, PB wants to be a Service, since third-party references mean it will
1011 need to make connections to arbitrary targets, and it may want to re-use
1012 those connections.
1013
1014  s = pb.PBService()
1015  s.listenOn(strport) # provides URL base
1016  swissURL = s.registerReference(ref)  # creates unguessable name
1017  publicURL = s.registerReference(ref, "name") # human-readable name
1018  s.unregister(URL) # also revokes all clids
1019  s.unregisterReference(ref)
1020  d = s.getReference(URL) # Deferred which fires with the RemoteReference
1021  d = s.shutdown() # close all servers and client connections
1022
1023 DONE, this makes things quite clean
1024
1025 * promise pipelining
1026
1027 Even without third-party references, we can do E-style promise pipelining.
1028
1029 <warner> hmm. subclass of Deferred that represents a Promise, can be
1030  serialized if it's being sent to the same broker as the RemoteReference it was
1031  generated for
1032 <dash> warner: hmmm. how's that help us?
1033 <dash> oh, pipelining?
1034 <warner> maybe a flag on the callRemote to say that "yeah, I want a
1035  DeferredPromise out of you, but I'm only going to include it as an argument to
1036  another method call I'm sending you, so don't bother sending *me* the result"
1037 <dash> aah
1038 <dash> yeah
1039 <dash> that sounds like a reasonable approach
1040 <warner> that would actually work
1041 <warner> dash: do you know if E makes any attempt to handle >2 vats in their
1042  pipelining implementation? seems to me it could turn into a large network
1043  optimization problem pretty quickly
1044 <dash> warner: Mmm
1045 <warner> hmm
1046 <dash> I do not think you have to
1047 <warner> so you have: t1=a.callRemote("foo",args1);
1048  t2=t1.callRemote("bar",args2), where callRemote returns a Promise, which is a
1049  special kind of Deferred that remembers the Broker its answer will eventually
1050  come from. If args2 consists of entirely immediate things (no Promises) or
1051  Promises that are coming from the same broker as t1 uses, then the "bar" call
1052  is eligible for pipelining and gets sent to the remote broker
1053 <warner> in the resulting newpb banana sequence, the clid of the target method
1054  is replaced by another kind of clid, which means "the answer you're going to
1055  send to method call #N", where N comes from t1
1056 <dash> mmm yep
1057 <warner> using that new I-can't-unserialize-this-yet hook we added, the second
1058  call sequence doesn't finish unserializing until the first call finishes and
1059  sends the answer. Sending answer #N fires the hook's deferred.
1060 <warner> that triggers the invocation of the second method
1061 <dash> yay
1062 <warner> hm, of course that totally blows away the idea of using a Constraint
1063  on the arguments to the second method
1064 <warner> because you don't even know what the object is until after the
1065  arguments have arrived
1066 <warner> but
1067 <dash> well
1068 <warner> the first method has a schema, which includes a return constraint
1069 <dash> okay you can't fail synchronously
1070 <warner> so you *can* assert that, whatever the object will be, it obeys that
1071  constraint
1072 <dash> but you can return a failure like everybody else
1073 <warner> and since the constraint specifies an Interface, then the Interface
1074  plus mehtod name is enough to come up with an argument constraint
1075 <warner> so you can still enforce one
1076 <warner> this is kind of cool
1077 <dash> the big advantage of pipelining is that you can have a lot of
1078  composable primitives on your remote interfaces rather than having to smush
1079  them together into things that are efficient to call remotely
1080 <warner> hm, yeah, as long as all the arguments are either immediate or
1081  reference something on the recipient
1082 <warner> as soon as a third party enters the equation, you have to decide
1083  whether to wait for the arguments to resolve locally or if it might be faster
1084  to throw them at someone else
1085 <warner> that's where the network-optimization thing I mentioned before comes
1086  into play
1087 <dash> mmm
1088 <warner> you send messages to A and to B, once you get both results you want
1089  to send the pair to C to do something with them
1090 <dash> spin me an example scenario
1091 <dash> Hmm
1092 <warner> if all three are close to each other, and you're far from all of
1093  them, it makes more sense to tell C about A and B
1094 <dash> how _does_ E handle that
1095 <warner> or maybe tell A and B about C, tell them "when you get done, send
1096  your results to C, who will be waiting for them"
1097 <dash> warner: yeah, i think that the right thing to do is to wait for them to
1098  resolve locally
1099 <Tv> assuming that C can talk to A and B is bad
1100 <dash> no it isn't
1101 <Tv> well, depends on whether you live in this world or not :)
1102 <dash> warner: if you want other behaviour then you should have to set it up
1103  explicitly, i think
1104 <warner> I'm not even sure how you would describe that sort of thing. It'd be
1105  like routing protocols, you assign a cost to each link and hope some magical
1106  omniscient entity can pick an optimal solution
1107
1108 ** revealing intentions
1109
1110 <zooko> Now suppose I say "B.your_fired(C.revoke_his_rights())", or such.
1111 <warner> A->C: sell all my stock. A->B: declare bankruptcy
1112
1113 If B has access to C, and the promises are pipelined, then B has a window
1114 during which they know something's about to happen, and they still have full
1115 access to C, so they can do evil.
1116
1117 Zooko tried to explain the concern to MarkM years ago, but didn't have a
1118 clear example of the problem. The thing is, B can do evil all the time,
1119 you're just trying to revoke their capability *before* they get wind of your
1120 intentions. Keeping intentions secret is hard, much harder than limiting
1121 someone's capabilities. It's kind of the trailing edge of the capability, as
1122 opposed to the leading edge.
1123
1124 Zooko feels the language needs clear support for expressing how the
1125 synchronization needs to take place, and which domain it needs to happen in.
1126
1127 * web-calculus integration
1128
1129 Tyler pointed out that it is vital for a node to be able to grant limited
1130 access to some held object. Specifically, Alice may want to give Bob a
1131 reference not to Carol as a whole, but to just a specific Carol.remote_foo
1132 method (and not to any other methods that Alice might be allowed to invoke).
1133 I had been thinking of using RemoteInterfaces to indicate method subsets,
1134 something like this:
1135